pax_global_header00006660000000000000000000000064146373552510014525gustar00rootroot0000000000000052 comment=07bbd001753f9633a614f6c0949d3d73142433d0 smithy-go-1.20.3/000077500000000000000000000000001463735525100135305ustar00rootroot00000000000000smithy-go-1.20.3/.changelog/000077500000000000000000000000001463735525100155355ustar00rootroot00000000000000smithy-go-1.20.3/.changelog/df65633a5ad44d44824adf6b784199e7.json000066400000000000000000000003401463735525100227470ustar00rootroot00000000000000{ "id": "df65633a-5ad4-4d44-824a-df6b784199e7", "type": "feature", "description": "Add ctx into endpoint param binding to support accountID-based endpoint routing downstream", "modules": [ "." ] }smithy-go-1.20.3/.github/000077500000000000000000000000001463735525100150705ustar00rootroot00000000000000smithy-go-1.20.3/.github/CODEOWNERS000066400000000000000000000001211463735525100164550ustar00rootroot00000000000000# Add core contributors to all PRs by default * @aws/aws-sdk-go-team @aws/smithy smithy-go-1.20.3/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000002511463735525100206670ustar00rootroot00000000000000*Issue #, if available:* *Description of changes:* By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. smithy-go-1.20.3/.github/labels.yml000066400000000000000000000057611463735525100170660ustar00rootroot00000000000000- name: bug color: d73a4a description: "This issue is a bug." - name: duplicate color: cfd8d7 description: "This issue is a duplicate." - name: needs-triage color: c7cafc description: "This issue or PR still needs to be triaged." - name: investigating color: 0000ff description: "This issue is being investigated and/or work is in progress to resolve the issue." - name: response-requested color: "666666" description: "Waiting on additional info and feedback. Will move to 'closing-soon' in 5 days." - name: closing-soon color: "000000" description: "This issue will automatically close in 2 days unless further comments are made." - name: feature-request color: 008672 description: "A feature should be added or improved." - name: guidance color: 8a19e7 description: "Question that needs advice or information." - name: documentation color: f7bc35 description: "This is a problem with documentation." - name: third-party color: 40C8B6 description: "This issue is related to third-party libraries or applications." - name: dependencies color: 40C8B6 description: "This issue is a problem in a dependency." - name: blocked color: 86499B description: "Work is blocked on this issue for this codebase. Other labels or comments may indicate why." - name: pending-release color: 86499B description: "This issue will be fixed by an approved PR that hasn't been released yet." - name: service-api color: E06A18 description: "This issue is due to a problem in a service API, not the SDK implementation." - name: breaking-change color: d85658 description: "This issue requires a breaking change to remediate." - name: needs-reproduction color: ffe79b description: "This issue needs reproduction." - name: needs-discussion color: ffe79b description: "This issue/PR requires more discussion with community." - name: SECURITY color: d85658 description: "" - name: help wanted color: ffe79b description: "We are asking the community to submit a PR to resolve this issue." - name: wontfix color: d85658 description: "We have determined that we will not resolve the issue" - name: pr/do-not-merge color: 358F18 description: "This PR should not be merged at this time." - name: pr/breaking-change color: 358F18 description: "This PR is a breaking change. It needs to be modified to be allowed in the current major version." - name: pr/blocked color: 358F18 description: "This PR cannot be merged or reviewed, because it is blocked for some reason." - name: pr/work-in-progress color: 358F18 description: "This PR is a draft and needs further work." - name: needs-review color: f996e2 description: "" - name: pr/ready-to-merge color: 358F18 description: "This PR is ready to be merged." - name: pr/needs-tests color: 358F18 description: "This PR is missing tests." - name: contribution/core color: DA5F98 description: "This is a PR that came from AWS." - name: no-auto-closure color: 009698 description: "We want this issue to not be automatically closed." smithy-go-1.20.3/.github/workflows/000077500000000000000000000000001463735525100171255ustar00rootroot00000000000000smithy-go-1.20.3/.github/workflows/api_diff_check.yml000066400000000000000000000010361463735525100225460ustar00rootroot00000000000000name: Go on: push: branches: [ main ] pull_request: branches: [ '*' ] jobs: build: name: API Diff Check runs-on: ubuntu-latest steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: go-version: "1.20" id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Get dependencies run: | (cd /tmp && go install golang.org/x/exp/cmd/gorelease@latest) - name: Check APIs run: $(go env GOPATH)/bin/gorelease smithy-go-1.20.3/.github/workflows/codegen.yml000066400000000000000000000016731463735525100212630ustar00rootroot00000000000000name: Codegen Tests on: push: branches: [ main ] pull_request: branches: [ main ] jobs: codegen-test: name: SDK Codegen Test runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest] go-version: ["1.20"] env: JAVA_TOOL_OPTIONS: "-Xmx2g" steps: - uses: actions/checkout@v2 - name: Download Coretto 17 JDK run: | download_url="https://corretto.aws/downloads/latest/amazon-corretto-17-x64-linux-jdk.tar.gz" wget -O $RUNNER_TEMP/java_package.tar.gz $download_url - name: Set up Coretto 17 JDK uses: actions/setup-java@v2 with: distribution: 'jdkfile' jdkFile: ${{ runner.temp }}/java_package.tar.gz java-version: 17 architecture: x64 - uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - name: SDK Codegen run: cd codegen && ./gradlew clean build -Plog-tests smithy-go-1.20.3/.github/workflows/go.yml000066400000000000000000000007621463735525100202620ustar00rootroot00000000000000name: Go Tests on: push: branches: [ main ] pull_request: branches: [ main ] jobs: unit-tests: name: SDK Unit Tests runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] go-version: ["1.20", "1.21", "1.22"] steps: - uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - name: Test run: go test -v ./... smithy-go-1.20.3/.github/workflows/labeler.yml000066400000000000000000000006671463735525100212670ustar00rootroot00000000000000name: github on: push: branches: - "main" jobs: labeler: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 - name: Run Labeler if: success() uses: crazy-max/ghaction-github-labeler@v1 with: yaml_file: .github/labels.yml skip_delete: true dry_run: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} smithy-go-1.20.3/.gitignore000066400000000000000000000003311463735525100155150ustar00rootroot00000000000000# Eclipse .classpath .project .settings/ # Intellij .idea/ *.iml *.iws # Mac .DS_Store # Maven target/ **/dependency-reduced-pom.xml # Gradle /.gradle build/ */out/ */*/out/ # VS Code bin/ .vscode/ # make c.out smithy-go-1.20.3/.travis.yml000066400000000000000000000005711463735525100156440ustar00rootroot00000000000000language: go sudo: true dist: bionic branches: only: - main os: - linux - osx # Travis doesn't work with windows and Go tip #- windows go: - tip matrix: allow_failures: - go: tip before_install: - if [ "$TRAVIS_OS_NAME" = "windows" ]; then choco install make; fi - (cd /tmp/; go get golang.org/x/lint/golint) script: - make go test -v ./...; smithy-go-1.20.3/CHANGELOG.md000066400000000000000000000212511463735525100153420ustar00rootroot00000000000000# Release (2024-06-27) ## Module Highlights * `github.com/aws/smithy-go`: v1.20.3 * **Bug Fix**: Fix encoding/cbor test overflow on x86. # Release (2024-03-29) * No change notes available for this release. # Release (2024-02-21) ## Module Highlights * `github.com/aws/smithy-go`: v1.20.1 * **Bug Fix**: Remove runtime dependency on go-cmp. # Release (2024-02-13) ## Module Highlights * `github.com/aws/smithy-go`: v1.20.0 * **Feature**: Add codegen definition for sigv4a trait. * **Feature**: Bump minimum Go version to 1.20 per our language support policy. # Release (2023-12-07) ## Module Highlights * `github.com/aws/smithy-go`: v1.19.0 * **Feature**: Support modeled request compression. # Release (2023-11-30) * No change notes available for this release. # Release (2023-11-29) ## Module Highlights * `github.com/aws/smithy-go`: v1.18.0 * **Feature**: Expose Options() method on generated service clients. # Release (2023-11-15) ## Module Highlights * `github.com/aws/smithy-go`: v1.17.0 * **Feature**: Support identity/auth components of client reference architecture. # Release (2023-10-31) ## Module Highlights * `github.com/aws/smithy-go`: v1.16.0 * **Feature**: **LANG**: Bump minimum go version to 1.19. # Release (2023-10-06) ## Module Highlights * `github.com/aws/smithy-go`: v1.15.0 * **Feature**: Add `http.WithHeaderComment` middleware. # Release (2023-08-18) * No change notes available for this release. # Release (2023-08-07) ## Module Highlights * `github.com/aws/smithy-go`: v1.14.1 * **Bug Fix**: Prevent duplicated error returns in EndpointResolverV2 default implementation. # Release (2023-07-31) ## General Highlights * **Feature**: Adds support for smithy-modeled endpoint resolution. # Release (2022-12-02) * No change notes available for this release. # Release (2022-10-24) ## Module Highlights * `github.com/aws/smithy-go`: v1.13.4 * **Bug Fix**: fixed document type checking for encoding nested types # Release (2022-09-14) * No change notes available for this release. # Release (v1.13.2) * No change notes available for this release. # Release (v1.13.1) * No change notes available for this release. # Release (v1.13.0) ## Module Highlights * `github.com/aws/smithy-go`: v1.13.0 * **Feature**: Adds support for the Smithy httpBearerAuth authentication trait to smithy-go. This allows the SDK to support the bearer authentication flow for API operations decorated with httpBearerAuth. An API client will need to be provided with its own bearer.TokenProvider implementation or use the bearer.StaticTokenProvider implementation. # Release (v1.12.1) ## Module Highlights * `github.com/aws/smithy-go`: v1.12.1 * **Bug Fix**: Fixes a bug where JSON object keys were not escaped. # Release (v1.12.0) ## Module Highlights * `github.com/aws/smithy-go`: v1.12.0 * **Feature**: `transport/http`: Add utility for setting context metadata when operation serializer automatically assigns content-type default value. # Release (v1.11.3) ## Module Highlights * `github.com/aws/smithy-go`: v1.11.3 * **Dependency Update**: Updates smithy-go unit test dependency go-cmp to 0.5.8. # Release (v1.11.2) * No change notes available for this release. # Release (v1.11.1) ## Module Highlights * `github.com/aws/smithy-go`: v1.11.1 * **Bug Fix**: Updates the smithy-go HTTP Request to correctly handle building the request to an http.Request. Related to [aws/aws-sdk-go-v2#1583](https://github.com/aws/aws-sdk-go-v2/issues/1583) # Release (v1.11.0) ## Module Highlights * `github.com/aws/smithy-go`: v1.11.0 * **Feature**: Updates deserialization of header list to supported quoted strings # Release (v1.10.0) ## Module Highlights * `github.com/aws/smithy-go`: v1.10.0 * **Feature**: Add `ptr.Duration`, `ptr.ToDuration`, `ptr.DurationSlice`, `ptr.ToDurationSlice`, `ptr.DurationMap`, and `ptr.ToDurationMap` functions for the `time.Duration` type. # Release (v1.9.1) ## Module Highlights * `github.com/aws/smithy-go`: v1.9.1 * **Documentation**: Fixes various typos in Go package documentation. # Release (v1.9.0) ## Module Highlights * `github.com/aws/smithy-go`: v1.9.0 * **Feature**: sync: OnceErr, can be used to concurrently record a signal when an error has occurred. * **Bug Fix**: `transport/http`: CloseResponseBody and ErrorCloseResponseBody middleware have been updated to ensure that the body is fully drained before closing. # Release v1.8.1 ### Smithy Go Module * **Bug Fix**: Fixed an issue that would cause the HTTP Content-Length to be set to 0 if the stream body was not set. * Fixes [aws/aws-sdk-go-v2#1418](https://github.com/aws/aws-sdk-go-v2/issues/1418) # Release v1.8.0 ### Smithy Go Module * `time`: Add support for parsing additional DateTime timestamp format ([#324](https://github.com/aws/smithy-go/pull/324)) * Adds support for parsing DateTime timestamp formatted time similar to RFC 3339, but without the `Z` character, nor UTC offset. * Fixes [#1387](https://github.com/aws/aws-sdk-go-v2/issues/1387) # Release v1.7.0 ### Smithy Go Module * `ptr`: Handle error for deferred file close call ([#314](https://github.com/aws/smithy-go/pull/314)) * Handle error for defer close call * `middleware`: Add Clone to Metadata ([#318](https://github.com/aws/smithy-go/pull/318)) * Adds a new Clone method to the middleware Metadata type. This provides a shallow clone of the entries in the Metadata. * `document`: Add new package for document shape serialization support ([#310](https://github.com/aws/smithy-go/pull/310)) ### Codegen * Add Smithy Document Shape Support ([#310](https://github.com/aws/smithy-go/pull/310)) * Adds support for Smithy Document shapes and supporting types for protocols to implement support # Release v1.6.0 (2021-07-15) ### Smithy Go Module * `encoding/httpbinding`: Support has been added for encoding `float32` and `float64` values that are `NaN`, `Infinity`, or `-Infinity`. ([#316](https://github.com/aws/smithy-go/pull/316)) ### Codegen * Adds support for handling `float32` and `float64` `NaN` values in HTTP Protocol Unit Tests. ([#316](https://github.com/aws/smithy-go/pull/316)) * Adds support protocol generator implementations to override the error code string returned by `ErrorCode` methods on generated error types. ([#315](https://github.com/aws/smithy-go/pull/315)) # Release v1.5.0 (2021-06-25) ### Smithy Go module * `time`: Update time parsing to not be as strict for HTTPDate and DateTime ([#307](https://github.com/aws/smithy-go/pull/307)) * Fixes [#302](https://github.com/aws/smithy-go/issues/302) by changing time to UTC before formatting so no local offset time is lost. ### Codegen * Adds support for integrating client members via plugins ([#301](https://github.com/aws/smithy-go/pull/301)) * Fix serialization of enum types marked with payload trait ([#296](https://github.com/aws/smithy-go/pull/296)) * Update generation of API client modules to include a manifest of files generated ([#283](https://github.com/aws/smithy-go/pull/283)) * Update Group Java group ID for smithy-go generator ([#298](https://github.com/aws/smithy-go/pull/298)) * Support the delegation of determining the errors that can occur for an operation ([#304](https://github.com/aws/smithy-go/pull/304)) * Support for marking and documenting deprecated client config fields. ([#303](https://github.com/aws/smithy-go/pull/303)) # Release v1.4.0 (2021-05-06) ### Smithy Go module * `encoding/xml`: Fix escaping of Next Line and Line Start in XML Encoder ([#267](https://github.com/aws/smithy-go/pull/267)) ### Codegen * Add support for Smithy 1.7 ([#289](https://github.com/aws/smithy-go/pull/289)) * Add support for httpQueryParams location * Add support for model renaming conflict resolution with service closure # Release v1.3.1 (2021-04-08) ### Smithy Go module * `transport/http`: Loosen endpoint hostname validation to allow specifying port numbers. ([#279](https://github.com/aws/smithy-go/pull/279)) * `io`: Fix RingBuffer panics due to out of bounds index. ([#282](https://github.com/aws/smithy-go/pull/282)) # Release v1.3.0 (2021-04-01) ### Smithy Go module * `transport/http`: Add utility to safely join string to url path, and url raw query. ### Codegen * Update HttpBindingProtocolGenerator to use http/transport JoinPath and JoinQuery utility. # Release v1.2.0 (2021-03-12) ### Smithy Go module * Fix support for parsing shortened year format in HTTP Date header. * Fix GitHub APIDiff action workflow to get gorelease tool correctly. * Fix codegen artifact unit test for Go 1.16 ### Codegen * Fix generating paginator nil parameter handling before usage. * Fix Serialize unboxed members decorated as required. * Add ability to define resolvers at both client construction and operation invocation. * Support for extending paginators with custom runtime trait smithy-go-1.20.3/CODE_OF_CONDUCT.md000066400000000000000000000004651463735525100163340ustar00rootroot00000000000000## Code of Conduct This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact opensource-codeofconduct@amazon.com with any additional questions or comments. smithy-go-1.20.3/CONTRIBUTING.md000066400000000000000000000061301463735525100157610ustar00rootroot00000000000000# Contributing Guidelines Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional documentation, we greatly value feedback and contributions from our community. Please read through this document before submitting any issues or pull requests to ensure we have all the necessary information to effectively respond to your bug report or contribution. ## Reporting Bugs/Feature Requests We welcome you to use the GitHub issue tracker to report bugs or suggest features. When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: * A reproducible test case or series of steps * The version of our code being used * Any modifications you've made relevant to the bug * Anything unusual about your environment or deployment ## Contributing via Pull Requests Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 1. You are working against the latest source on the *main* branch. 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. To send us a pull request, please: 1. Fork the repository. 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 3. Ensure local tests pass. 4. Commit to your fork using clear commit messages. 5. Send us a pull request, answering any default questions in the pull request interface. 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). ## Finding contributions to work on Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. ## Code of Conduct This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact opensource-codeofconduct@amazon.com with any additional questions or comments. ## Security issue notifications If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. ## Licensing See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. smithy-go-1.20.3/LICENSE000066400000000000000000000236361463735525100145470ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. smithy-go-1.20.3/Makefile000066400000000000000000000064661463735525100152040ustar00rootroot00000000000000PRE_RELEASE_VERSION ?= RELEASE_MANIFEST_FILE ?= RELEASE_CHGLOG_DESC_FILE ?= REPOTOOLS_VERSION ?= latest REPOTOOLS_MODULE = github.com/awslabs/aws-go-multi-module-repository-tools REPOTOOLS_CMD_CALCULATE_RELEASE = ${REPOTOOLS_MODULE}/cmd/calculaterelease@${REPOTOOLS_VERSION} REPOTOOLS_CMD_CALCULATE_RELEASE_ADDITIONAL_ARGS ?= REPOTOOLS_CMD_UPDATE_REQUIRES = ${REPOTOOLS_MODULE}/cmd/updaterequires@${REPOTOOLS_VERSION} REPOTOOLS_CMD_UPDATE_MODULE_METADATA = ${REPOTOOLS_MODULE}/cmd/updatemodulemeta@${REPOTOOLS_VERSION} REPOTOOLS_CMD_GENERATE_CHANGELOG = ${REPOTOOLS_MODULE}/cmd/generatechangelog@${REPOTOOLS_VERSION} REPOTOOLS_CMD_CHANGELOG = ${REPOTOOLS_MODULE}/cmd/changelog@${REPOTOOLS_VERSION} REPOTOOLS_CMD_TAG_RELEASE = ${REPOTOOLS_MODULE}/cmd/tagrelease@${REPOTOOLS_VERSION} REPOTOOLS_CMD_MODULE_VERSION = ${REPOTOOLS_MODULE}/cmd/moduleversion@${REPOTOOLS_VERSION} UNIT_TEST_TAGS= BUILD_TAGS= ifneq ($(PRE_RELEASE_VERSION),) REPOTOOLS_CMD_CALCULATE_RELEASE_ADDITIONAL_ARGS += -preview=${PRE_RELEASE_VERSION} endif smithy-publish-local: cd codegen && ./gradlew publishToMavenLocal smithy-build: cd codegen && ./gradlew build smithy-clean: cd codegen && ./gradlew clean ################## # Linting/Verify # ################## .PHONY: verify vet cover verify: vet vet: go vet ${BUILD_TAGS} --all ./... cover: go test ${BUILD_TAGS} -coverprofile c.out ./... @cover=`go tool cover -func c.out | grep '^total:' | awk '{ print $$3+0 }'`; \ echo "total (statements): $$cover%"; ################ # Unit Testing # ################ .PHONY: unit unit-race unit-test unit-race-test unit: verify go vet ${BUILD_TAGS} --all ./... && \ go test ${BUILD_TAGS} ${RUN_NONE} ./... && \ go test -timeout=1m ${UNIT_TEST_TAGS} ./... unit-race: verify go vet ${BUILD_TAGS} --all ./... && \ go test ${BUILD_TAGS} ${RUN_NONE} ./... && \ go test -timeout=1m ${UNIT_TEST_TAGS} -race -cpu=4 ./... unit-test: verify go test -timeout=1m ${UNIT_TEST_TAGS} ./... unit-race-test: verify go test -timeout=1m ${UNIT_TEST_TAGS} -race -cpu=4 ./... ##################### # Release Process # ##################### .PHONY: preview-release pre-release-validation release preview-release: go run ${REPOTOOLS_CMD_CALCULATE_RELEASE} ${REPOTOOLS_CMD_CALCULATE_RELEASE_ADDITIONAL_ARGS} pre-release-validation: @if [[ -z "${RELEASE_MANIFEST_FILE}" ]]; then \ echo "RELEASE_MANIFEST_FILE is required to specify the file to write the release manifest" && false; \ fi @if [[ -z "${RELEASE_CHGLOG_DESC_FILE}" ]]; then \ echo "RELEASE_CHGLOG_DESC_FILE is required to specify the file to write the release notes" && false; \ fi release: pre-release-validation go run ${REPOTOOLS_CMD_CALCULATE_RELEASE} -o ${RELEASE_MANIFEST_FILE} ${REPOTOOLS_CMD_CALCULATE_RELEASE_ADDITIONAL_ARGS} go run ${REPOTOOLS_CMD_UPDATE_REQUIRES} -release ${RELEASE_MANIFEST_FILE} go run ${REPOTOOLS_CMD_UPDATE_MODULE_METADATA} -release ${RELEASE_MANIFEST_FILE} go run ${REPOTOOLS_CMD_GENERATE_CHANGELOG} -release ${RELEASE_MANIFEST_FILE} -o ${RELEASE_CHGLOG_DESC_FILE} go run ${REPOTOOLS_CMD_CHANGELOG} rm -all go run ${REPOTOOLS_CMD_TAG_RELEASE} -release ${RELEASE_MANIFEST_FILE} module-version: @go run ${REPOTOOLS_CMD_MODULE_VERSION} . ############## # Repo Tools # ############## .PHONY: install-changelog install-changelog: go install ${REPOTOOLS_MODULE}/cmd/changelog@${REPOTOOLS_VERSION} smithy-go-1.20.3/NOTICE000066400000000000000000000001031463735525100144260ustar00rootroot00000000000000Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. smithy-go-1.20.3/README.md000066400000000000000000000026471463735525100150200ustar00rootroot00000000000000## Smithy Go [![Go Build Status](https://github.com/aws/smithy-go/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/aws/smithy-go/actions/workflows/go.yml)[![Codegen Build Status](https://github.com/aws/smithy-go/actions/workflows/codegen.yml/badge.svg?branch=main)](https://github.com/aws/smithy-go/actions/workflows/codegen.yml) [Smithy](https://smithy.io/) code generators for Go. **WARNING: All interfaces are subject to change.** ## Can I use this? In order to generate a usable smithy client you must provide a [protocol definition](https://github.com/aws/smithy-go/blob/main/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java), such as [AWS restJson1](https://smithy.io/2.0/aws/protocols/aws-restjson1-protocol.html), in order to generate transport mechanisms and serialization/deserialization code ("serde") accordingly. The code generator does not currently support any protocols out of the box, therefore the useability of this project on its own is currently limited. Support for all [AWS protocols](https://smithy.io/2.0/aws/protocols/index.html) exists in [aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2). We are tracking the movement of those out of the SDK into smithy-go in [#458](https://github.com/aws/smithy-go/issues/458), but there's currently no timeline for doing so. ## License This project is licensed under the Apache-2.0 License. smithy-go-1.20.3/auth/000077500000000000000000000000001463735525100144715ustar00rootroot00000000000000smithy-go-1.20.3/auth/auth.go000066400000000000000000000001431463735525100157570ustar00rootroot00000000000000// Package auth defines protocol-agnostic authentication types for smithy // clients. package auth smithy-go-1.20.3/auth/bearer/000077500000000000000000000000001463735525100157315ustar00rootroot00000000000000smithy-go-1.20.3/auth/bearer/docs.go000066400000000000000000000002021463735525100172020ustar00rootroot00000000000000// Package bearer provides middleware and utilities for authenticating API // operation calls with a Bearer Token. package bearer smithy-go-1.20.3/auth/bearer/middleware.go000066400000000000000000000067171463735525100204100ustar00rootroot00000000000000package bearer import ( "context" "fmt" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" ) // Message is the middleware stack's request transport message value. type Message interface{} // Signer provides an interface for implementations to decorate a request // message with a bearer token. The signer is responsible for validating the // message type is compatible with the signer. type Signer interface { SignWithBearerToken(context.Context, Token, Message) (Message, error) } // AuthenticationMiddleware provides the Finalize middleware step for signing // an request message with a bearer token. type AuthenticationMiddleware struct { signer Signer tokenProvider TokenProvider } // AddAuthenticationMiddleware helper adds the AuthenticationMiddleware to the // middleware Stack in the Finalize step with the options provided. func AddAuthenticationMiddleware(s *middleware.Stack, signer Signer, tokenProvider TokenProvider) error { return s.Finalize.Add( NewAuthenticationMiddleware(signer, tokenProvider), middleware.After, ) } // NewAuthenticationMiddleware returns an initialized AuthenticationMiddleware. func NewAuthenticationMiddleware(signer Signer, tokenProvider TokenProvider) *AuthenticationMiddleware { return &AuthenticationMiddleware{ signer: signer, tokenProvider: tokenProvider, } } const authenticationMiddlewareID = "BearerTokenAuthentication" // ID returns the resolver identifier func (m *AuthenticationMiddleware) ID() string { return authenticationMiddlewareID } // HandleFinalize implements the FinalizeMiddleware interface in order to // update the request with bearer token authentication. func (m *AuthenticationMiddleware) HandleFinalize( ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, ) ( out middleware.FinalizeOutput, metadata middleware.Metadata, err error, ) { token, err := m.tokenProvider.RetrieveBearerToken(ctx) if err != nil { return out, metadata, fmt.Errorf("failed AuthenticationMiddleware wrap message, %w", err) } signedMessage, err := m.signer.SignWithBearerToken(ctx, token, in.Request) if err != nil { return out, metadata, fmt.Errorf("failed AuthenticationMiddleware sign message, %w", err) } in.Request = signedMessage return next.HandleFinalize(ctx, in) } // SignHTTPSMessage provides a bearer token authentication implementation that // will sign the message with the provided bearer token. // // Will fail if the message is not a smithy-go HTTP request or the request is // not HTTPS. type SignHTTPSMessage struct{} // NewSignHTTPSMessage returns an initialized signer for HTTP messages. func NewSignHTTPSMessage() *SignHTTPSMessage { return &SignHTTPSMessage{} } // SignWithBearerToken returns a copy of the HTTP request with the bearer token // added via the "Authorization" header, per RFC 6750, https://datatracker.ietf.org/doc/html/rfc6750. // // Returns an error if the request's URL scheme is not HTTPS, or the request // message is not an smithy-go HTTP Request pointer type. func (SignHTTPSMessage) SignWithBearerToken(ctx context.Context, token Token, message Message) (Message, error) { req, ok := message.(*smithyhttp.Request) if !ok { return nil, fmt.Errorf("expect smithy-go HTTP Request, got %T", message) } if !req.IsHTTPS() { return nil, fmt.Errorf("bearer token with HTTP request requires HTTPS") } reqClone := req.Clone() reqClone.Header.Set("Authorization", "Bearer "+token.Value) return reqClone, nil } smithy-go-1.20.3/auth/bearer/middleware_test.go000066400000000000000000000035021463735525100214340ustar00rootroot00000000000000package bearer import ( "context" "net/url" "reflect" "strings" "testing" smithyhttp "github.com/aws/smithy-go/transport/http" ) func TestSignHTTPSMessage(t *testing.T) { cases := map[string]struct { message Message token Token expectMessage Message expectErr string }{ // Cases "not smithyhttp.Request": { message: struct{}{}, expectErr: "expect smithy-go HTTP Request", }, "not https": { message: func() Message { r := smithyhttp.NewStackRequest().(*smithyhttp.Request) r.URL, _ = url.Parse("http://example.aws") return r }(), expectErr: "requires HTTPS", }, "success": { message: func() Message { r := smithyhttp.NewStackRequest().(*smithyhttp.Request) r.URL, _ = url.Parse("https://example.aws") return r }(), token: Token{Value: "abc123"}, expectMessage: func() Message { r := smithyhttp.NewStackRequest().(*smithyhttp.Request) r.URL, _ = url.Parse("https://example.aws") r.Header.Set("Authorization", "Bearer abc123") return r }(), }, } for name, c := range cases { t.Run(name, func(t *testing.T) { ctx := context.Background() signer := SignHTTPSMessage{} message, err := signer.SignWithBearerToken(ctx, c.token, c.message) if c.expectErr != "" { if err == nil { t.Fatalf("expect error, got none") } if e, a := c.expectErr, err.Error(); !strings.Contains(a, e) { t.Fatalf("expect %v in error %v", e, a) } return } else if err != nil { t.Fatalf("expect no error, got %v", err) } expect := c.expectMessage.(*smithyhttp.Request) actual, ok := message.(*smithyhttp.Request) if !ok { t.Fatalf("*smithyhttp.Request != %T", actual) } if !reflect.DeepEqual(expect.Header, actual.Header) { t.Errorf("%v != %v", expect.Header, actual.Header) } }) } } smithy-go-1.20.3/auth/bearer/token.go000066400000000000000000000025411463735525100174020ustar00rootroot00000000000000package bearer import ( "context" "time" ) // Token provides a type wrapping a bearer token and expiration metadata. type Token struct { Value string CanExpire bool Expires time.Time } // Expired returns if the token's Expires time is before or equal to the time // provided. If CanExpires is false, Expired will always return false. func (t Token) Expired(now time.Time) bool { if !t.CanExpire { return false } now = now.Round(0) return now.Equal(t.Expires) || now.After(t.Expires) } // TokenProvider provides interface for retrieving bearer tokens. type TokenProvider interface { RetrieveBearerToken(context.Context) (Token, error) } // TokenProviderFunc provides a helper utility to wrap a function as a type // that implements the TokenProvider interface. type TokenProviderFunc func(context.Context) (Token, error) // RetrieveBearerToken calls the wrapped function, returning the Token or // error. func (fn TokenProviderFunc) RetrieveBearerToken(ctx context.Context) (Token, error) { return fn(ctx) } // StaticTokenProvider provides a utility for wrapping a static bearer token // value within an implementation of a token provider. type StaticTokenProvider struct { Token Token } // RetrieveBearerToken returns the static token specified. func (s StaticTokenProvider) RetrieveBearerToken(context.Context) (Token, error) { return s.Token, nil } smithy-go-1.20.3/auth/bearer/token_cache.go000066400000000000000000000161021463735525100205230ustar00rootroot00000000000000package bearer import ( "context" "fmt" "sync/atomic" "time" smithycontext "github.com/aws/smithy-go/context" "github.com/aws/smithy-go/internal/sync/singleflight" ) // package variable that can be override in unit tests. var timeNow = time.Now // TokenCacheOptions provides a set of optional configuration options for the // TokenCache TokenProvider. type TokenCacheOptions struct { // The duration before the token will expire when the credentials will be // refreshed. If DisableAsyncRefresh is true, the RetrieveBearerToken calls // will be blocking. // // Asynchronous refreshes are deduplicated, and only one will be in-flight // at a time. If the token expires while an asynchronous refresh is in // flight, the next call to RetrieveBearerToken will block on that refresh // to return. RefreshBeforeExpires time.Duration // The timeout the underlying TokenProvider's RetrieveBearerToken call must // return within, or will be canceled. Defaults to 0, no timeout. // // If 0 timeout, its possible for the underlying tokenProvider's // RetrieveBearerToken call to block forever. Preventing subsequent // TokenCache attempts to refresh the token. // // If this timeout is reached all pending deduplicated calls to // TokenCache RetrieveBearerToken will fail with an error. RetrieveBearerTokenTimeout time.Duration // The minimum duration between asynchronous refresh attempts. If the next // asynchronous recent refresh attempt was within the minimum delay // duration, the call to retrieve will return the current cached token, if // not expired. // // The asynchronous retrieve is deduplicated across multiple calls when // RetrieveBearerToken is called. The asynchronous retrieve is not a // periodic task. It is only performed when the token has not yet expired, // and the current item is within the RefreshBeforeExpires window, and the // TokenCache's RetrieveBearerToken method is called. // // If 0, (default) there will be no minimum delay between asynchronous // refresh attempts. // // If DisableAsyncRefresh is true, this option is ignored. AsyncRefreshMinimumDelay time.Duration // Sets if the TokenCache will attempt to refresh the token in the // background asynchronously instead of blocking for credentials to be // refreshed. If disabled token refresh will be blocking. // // The first call to RetrieveBearerToken will always be blocking, because // there is no cached token. DisableAsyncRefresh bool } // TokenCache provides an utility to cache Bearer Authentication tokens from a // wrapped TokenProvider. The TokenCache can be has options to configure the // cache's early and asynchronous refresh of the token. type TokenCache struct { options TokenCacheOptions provider TokenProvider cachedToken atomic.Value lastRefreshAttemptTime atomic.Value sfGroup singleflight.Group } // NewTokenCache returns a initialized TokenCache that implements the // TokenProvider interface. Wrapping the provider passed in. Also taking a set // of optional functional option parameters to configure the token cache. func NewTokenCache(provider TokenProvider, optFns ...func(*TokenCacheOptions)) *TokenCache { var options TokenCacheOptions for _, fn := range optFns { fn(&options) } return &TokenCache{ options: options, provider: provider, } } // RetrieveBearerToken returns the token if it could be obtained, or error if a // valid token could not be retrieved. // // The passed in Context's cancel/deadline/timeout will impacting only this // individual retrieve call and not any other already queued up calls. This // means underlying provider's RetrieveBearerToken calls could block for ever, // and not be canceled with the Context. Set RetrieveBearerTokenTimeout to // provide a timeout, preventing the underlying TokenProvider blocking forever. // // By default, if the passed in Context is canceled, all of its values will be // considered expired. The wrapped TokenProvider will not be able to lookup the // values from the Context once it is expired. This is done to protect against // expired values no longer being valid. To disable this behavior, use // smithy-go's context.WithPreserveExpiredValues to add a value to the Context // before calling RetrieveBearerToken to enable support for expired values. // // Without RetrieveBearerTokenTimeout there is the potential for a underlying // Provider's RetrieveBearerToken call to sit forever. Blocking in subsequent // attempts at refreshing the token. func (p *TokenCache) RetrieveBearerToken(ctx context.Context) (Token, error) { cachedToken, ok := p.getCachedToken() if !ok || cachedToken.Expired(timeNow()) { return p.refreshBearerToken(ctx) } // Check if the token should be refreshed before it expires. refreshToken := cachedToken.Expired(timeNow().Add(p.options.RefreshBeforeExpires)) if !refreshToken { return cachedToken, nil } if p.options.DisableAsyncRefresh { return p.refreshBearerToken(ctx) } p.tryAsyncRefresh(ctx) return cachedToken, nil } // tryAsyncRefresh attempts to asynchronously refresh the token returning the // already cached token. If it AsyncRefreshMinimumDelay option is not zero, and // the duration since the last refresh is less than that value, nothing will be // done. func (p *TokenCache) tryAsyncRefresh(ctx context.Context) { if p.options.AsyncRefreshMinimumDelay != 0 { var lastRefreshAttempt time.Time if v := p.lastRefreshAttemptTime.Load(); v != nil { lastRefreshAttempt = v.(time.Time) } if timeNow().Before(lastRefreshAttempt.Add(p.options.AsyncRefreshMinimumDelay)) { return } } // Ignore the returned channel so this won't be blocking, and limit the // number of additional goroutines created. p.sfGroup.DoChan("async-refresh", func() (interface{}, error) { res, err := p.refreshBearerToken(ctx) if p.options.AsyncRefreshMinimumDelay != 0 { var refreshAttempt time.Time if err != nil { refreshAttempt = timeNow() } p.lastRefreshAttemptTime.Store(refreshAttempt) } return res, err }) } func (p *TokenCache) refreshBearerToken(ctx context.Context) (Token, error) { resCh := p.sfGroup.DoChan("refresh-token", func() (interface{}, error) { ctx := smithycontext.WithSuppressCancel(ctx) if v := p.options.RetrieveBearerTokenTimeout; v != 0 { var cancel func() ctx, cancel = context.WithTimeout(ctx, v) defer cancel() } return p.singleRetrieve(ctx) }) select { case res := <-resCh: return res.Val.(Token), res.Err case <-ctx.Done(): return Token{}, fmt.Errorf("retrieve bearer token canceled, %w", ctx.Err()) } } func (p *TokenCache) singleRetrieve(ctx context.Context) (interface{}, error) { token, err := p.provider.RetrieveBearerToken(ctx) if err != nil { return Token{}, fmt.Errorf("failed to retrieve bearer token, %w", err) } p.cachedToken.Store(&token) return token, nil } // getCachedToken returns the currently cached token and true if found. Returns // false if no token is cached. func (p *TokenCache) getCachedToken() (Token, bool) { v := p.cachedToken.Load() if v == nil { return Token{}, false } t := v.(*Token) if t == nil || t.Value == "" { return Token{}, false } return *t, true } smithy-go-1.20.3/auth/bearer/token_cache_test.go000066400000000000000000000335331463735525100215710ustar00rootroot00000000000000package bearer import ( "context" "fmt" "strconv" "strings" "sync" "sync/atomic" "testing" "time" ) var _ TokenProvider = (*TokenCache)(nil) func TestTokenCache_cache(t *testing.T) { expectToken := Token{ Value: "abc123", } var retrieveCalled bool provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) { if retrieveCalled { t.Fatalf("expect wrapped provider to be called once") } retrieveCalled = true return expectToken, nil })) token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if expectToken != token { t.Errorf("expect token match: %v != %v", expectToken, token) } for i := 0; i < 100; i++ { token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if expectToken != token { t.Errorf("expect token match: %v != %v", expectToken, token) } } } func TestTokenCache_cacheConcurrent(t *testing.T) { expectToken := Token{ Value: "abc123", } var retrieveCalled bool provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) { if retrieveCalled { t.Fatalf("expect wrapped provider to be called once") } retrieveCalled = true return expectToken, nil })) token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if expectToken != token { t.Errorf("expect token match: %v != %v", expectToken, token) } for i := 0; i < 100; i++ { t.Run(strconv.Itoa(i), func(t *testing.T) { t.Parallel() token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if expectToken != token { t.Errorf("expect token match: %v != %v", expectToken, token) } }) } } func TestTokenCache_expired(t *testing.T) { origTimeNow := timeNow defer func() { timeNow = origTimeNow }() timeNow = func() time.Time { return time.Time{} } expectToken := Token{ Value: "abc123", CanExpire: true, Expires: timeNow().Add(10 * time.Minute), } refreshedToken := Token{ Value: "refreshed-abc123", CanExpire: true, Expires: timeNow().Add(30 * time.Minute), } retrievedCount := new(int32) provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) { if atomic.AddInt32(retrievedCount, 1) > 1 { return refreshedToken, nil } return expectToken, nil })) for i := 0; i < 10; i++ { token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if expectToken != token { t.Errorf("expect token match: %v != %v", expectToken, token) } } if e, a := 1, int(atomic.LoadInt32(retrievedCount)); e != a { t.Errorf("expect %v provider calls, got %v", e, a) } // Offset time for refresh timeNow = func() time.Time { return (time.Time{}).Add(10 * time.Minute) } token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if refreshedToken != token { t.Errorf("expect refreshed token match: %v != %v", refreshedToken, token) } if e, a := 2, int(atomic.LoadInt32(retrievedCount)); e != a { t.Errorf("expect %v provider calls, got %v", e, a) } } func TestTokenCache_cancelled(t *testing.T) { providerRunning := make(chan struct{}) providerDone := make(chan struct{}) var onceClose sync.Once provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) { onceClose.Do(func() { close(providerRunning) }) // Provider running never receives context cancel so that if the first // retrieve call is canceled all subsequent retrieve callers won't get // canceled as well. select { case <-providerDone: return Token{Value: "abc123"}, nil case <-ctx.Done(): return Token{}, fmt.Errorf("unexpected context canceled, %w", ctx.Err()) } })) ctx, cancel := context.WithCancel(context.Background()) cancel() // Retrieve that will have its context canceled, should return error, but // underlying provider retrieve will continue to block in the background. var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() _, err := provider.RetrieveBearerToken(ctx) if err == nil { t.Errorf("expect error, got none") } else if e, a := "unexpected context canceled", err.Error(); strings.Contains(a, e) { t.Errorf("unexpected context canceled received, %v", err) } else if e, a := "context canceled", err.Error(); !strings.Contains(a, e) { t.Errorf("expect %v error in, %v", e, a) } }() <-providerRunning // Retrieve that will be added to existing single flight group, (or create // a new group). Returning valid token. wg.Add(1) go func() { defer wg.Done() token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Errorf("expect no error, got %v", err) } else { expect := Token{Value: "abc123"} if expect != token { t.Errorf("expect token retrieve match: %v != %v", expect, token) } } }() close(providerDone) wg.Wait() } func TestTokenCache_cancelledWithTimeout(t *testing.T) { providerReady := make(chan struct{}) var providerReadCloseOnce sync.Once provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) { providerReadCloseOnce.Do(func() { close(providerReady) }) <-ctx.Done() return Token{}, fmt.Errorf("token retrieve timeout, %w", ctx.Err()) }), func(o *TokenCacheOptions) { o.RetrieveBearerTokenTimeout = time.Millisecond }) var wg sync.WaitGroup // Spin up additional retrieves that will be deduplicated and block on the // original retrieve call. for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() <-providerReady _, err := provider.RetrieveBearerToken(context.Background()) if err == nil { t.Errorf("expect error, got none") } else if e, a := "token retrieve timeout", err.Error(); !strings.Contains(a, e) { t.Errorf("expect %v error in, %v", e, a) } }() } _, err := provider.RetrieveBearerToken(context.Background()) if err == nil { t.Errorf("expect error, got none") } else if e, a := "token retrieve timeout", err.Error(); !strings.Contains(a, e) { t.Errorf("expect %v error in, %v", e, a) } wg.Wait() } func TestTokenCache_asyncRefresh(t *testing.T) { origTimeNow := timeNow defer func() { timeNow = origTimeNow }() timeNow = func() time.Time { return time.Time{} } expectToken := Token{ Value: "abc123", CanExpire: true, Expires: timeNow().Add(10 * time.Minute), } refreshedToken := Token{ Value: "refreshed-abc123", CanExpire: true, Expires: timeNow().Add(30 * time.Minute), } retrievedCount := new(int32) provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) { c := atomic.AddInt32(retrievedCount, 1) switch { case c == 1: return expectToken, nil case c > 1 && c < 5: return Token{}, fmt.Errorf("some error") case c == 5: return refreshedToken, nil default: return Token{}, fmt.Errorf("unexpected error") } }), func(o *TokenCacheOptions) { o.RefreshBeforeExpires = 5 * time.Minute }) // 1: Initial retrieve to cache token token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if expectToken != token { t.Errorf("expect token match: %v != %v", expectToken, token) } // 2-5: Offset time for subsequent calls to retrieve to trigger asynchronous // refreshes. timeNow = func() time.Time { return (time.Time{}).Add(6 * time.Minute) } for i := 0; i < 4; i++ { token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if expectToken != token { t.Errorf("expect token match: %v != %v", expectToken, token) } } // Wait for all async refreshes to complete testWaitAsyncRefreshDone(provider) if c := int(atomic.LoadInt32(retrievedCount)); c < 2 || c > 5 { t.Fatalf("expect async refresh to be called [2,5) times, got, %v", c) } // Ensure enough retrieves have been done to trigger refresh. if c := atomic.LoadInt32(retrievedCount); c != 5 { atomic.StoreInt32(retrievedCount, 4) token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if expectToken != token { t.Errorf("expect token match: %v != %v", expectToken, token) } testWaitAsyncRefreshDone(provider) } // Last async refresh will succeed and update cached token, expect the next // call to get refreshed token. token, err = provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if refreshedToken != token { t.Errorf("expect refreshed token match: %v != %v", refreshedToken, token) } } func TestTokenCache_asyncRefreshWithMinDelay(t *testing.T) { origTimeNow := timeNow defer func() { timeNow = origTimeNow }() timeNow = func() time.Time { return time.Time{} } expectToken := Token{ Value: "abc123", CanExpire: true, Expires: timeNow().Add(10 * time.Minute), } refreshedToken := Token{ Value: "refreshed-abc123", CanExpire: true, Expires: timeNow().Add(30 * time.Minute), } retrievedCount := new(int32) provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) { c := atomic.AddInt32(retrievedCount, 1) switch { case c == 1: return expectToken, nil case c > 1 && c < 5: return Token{}, fmt.Errorf("some error") case c == 5: return refreshedToken, nil default: return Token{}, fmt.Errorf("unexpected error") } }), func(o *TokenCacheOptions) { o.RefreshBeforeExpires = 5 * time.Minute o.AsyncRefreshMinimumDelay = 30 * time.Second }) // 1: Initial retrieve to cache token token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if expectToken != token { t.Errorf("expect token match: %v != %v", expectToken, token) } // 2-5: Offset time for subsequent calls to retrieve to trigger asynchronous // refreshes. timeNow = func() time.Time { return (time.Time{}).Add(6 * time.Minute) } for i := 0; i < 4; i++ { token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if expectToken != token { t.Errorf("expect token match: %v != %v", expectToken, token) } // Wait for all async refreshes to complete ensure not deduped testWaitAsyncRefreshDone(provider) } // Only a single refresh attempt is expected. if e, a := 2, int(atomic.LoadInt32(retrievedCount)); e != a { t.Fatalf("expect %v min async refresh, got %v", e, a) } // Move time forward to ensure another async refresh is triggered. timeNow = func() time.Time { return (time.Time{}).Add(7 * time.Minute) } // Make sure the next attempt refreshes the token atomic.StoreInt32(retrievedCount, 4) // Do async retrieve that will succeed refreshing in background. token, err = provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if expectToken != token { t.Errorf("expect token match: %v != %v", expectToken, token) } // Wait for all async refreshes to complete ensure not deduped testWaitAsyncRefreshDone(provider) // Last async refresh will succeed and update cached token, expect the next // call to get refreshed token. token, err = provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if refreshedToken != token { t.Errorf("expect refreshed token match: %v != %v", refreshedToken, token) } } func TestTokenCache_disableAsyncRefresh(t *testing.T) { origTimeNow := timeNow defer func() { timeNow = origTimeNow }() timeNow = func() time.Time { return time.Time{} } expectToken := Token{ Value: "abc123", CanExpire: true, Expires: timeNow().Add(10 * time.Minute), } refreshedToken := Token{ Value: "refreshed-abc123", CanExpire: true, Expires: timeNow().Add(30 * time.Minute), } retrievedCount := new(int32) provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) { c := atomic.AddInt32(retrievedCount, 1) switch { case c == 1: return expectToken, nil case c > 1 && c < 5: return Token{}, fmt.Errorf("some error") case c == 5: return refreshedToken, nil default: return Token{}, fmt.Errorf("unexpected error") } }), func(o *TokenCacheOptions) { o.RefreshBeforeExpires = 5 * time.Minute o.DisableAsyncRefresh = true }) // 1: Initial retrieve to cache token token, err := provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if expectToken != token { t.Errorf("expect token match: %v != %v", expectToken, token) } // Update time into refresh window before token expires timeNow = func() time.Time { return (time.Time{}).Add(6 * time.Minute) } for i := 0; i < 3; i++ { _, err = provider.RetrieveBearerToken(context.Background()) if err == nil { t.Fatalf("expect error, got none") } if e, a := "some error", err.Error(); !strings.Contains(a, e) { t.Fatalf("expect %v error in %v", e, a) } if e, a := i+2, int(atomic.LoadInt32(retrievedCount)); e != a { t.Fatalf("expect %v retrieveCount, got %v", e, a) } } if e, a := 4, int(atomic.LoadInt32(retrievedCount)); e != a { t.Fatalf("expect %v retrieveCount, got %v", e, a) } // Last refresh will succeed and update cached token, expect the next // call to get refreshed token. token, err = provider.RetrieveBearerToken(context.Background()) if err != nil { t.Fatalf("expect no error, got %v", err) } if refreshedToken != token { t.Errorf("expect refreshed token match: %v != %v", refreshedToken, token) } } func testWaitAsyncRefreshDone(provider *TokenCache) { asyncResCh := provider.sfGroup.DoChan("async-refresh", func() (interface{}, error) { return nil, nil }) <-asyncResCh } smithy-go-1.20.3/auth/identity.go000066400000000000000000000024121463735525100166500ustar00rootroot00000000000000package auth import ( "context" "time" "github.com/aws/smithy-go" ) // Identity contains information that identifies who the user making the // request is. type Identity interface { Expiration() time.Time } // IdentityResolver defines the interface through which an Identity is // retrieved. type IdentityResolver interface { GetIdentity(context.Context, smithy.Properties) (Identity, error) } // IdentityResolverOptions defines the interface through which an entity can be // queried to retrieve an IdentityResolver for a given auth scheme. type IdentityResolverOptions interface { GetIdentityResolver(schemeID string) IdentityResolver } // AnonymousIdentity is a sentinel to indicate no identity. type AnonymousIdentity struct{} var _ Identity = (*AnonymousIdentity)(nil) // Expiration returns the zero value for time, as anonymous identity never // expires. func (*AnonymousIdentity) Expiration() time.Time { return time.Time{} } // AnonymousIdentityResolver returns AnonymousIdentity. type AnonymousIdentityResolver struct{} var _ IdentityResolver = (*AnonymousIdentityResolver)(nil) // GetIdentity returns AnonymousIdentity. func (*AnonymousIdentityResolver) GetIdentity(_ context.Context, _ smithy.Properties) (Identity, error) { return &AnonymousIdentity{}, nil } smithy-go-1.20.3/auth/option.go000066400000000000000000000011431463735525100163270ustar00rootroot00000000000000package auth import "github.com/aws/smithy-go" type ( authOptionsKey struct{} ) // Option represents a possible authentication method for an operation. type Option struct { SchemeID string IdentityProperties smithy.Properties SignerProperties smithy.Properties } // GetAuthOptions gets auth Options from Properties. func GetAuthOptions(p *smithy.Properties) ([]*Option, bool) { v, ok := p.Get(authOptionsKey{}).([]*Option) return v, ok } // SetAuthOptions sets auth Options on Properties. func SetAuthOptions(p *smithy.Properties, options []*Option) { p.Set(authOptionsKey{}, options) } smithy-go-1.20.3/auth/option_test.go000066400000000000000000000010551463735525100173700ustar00rootroot00000000000000package auth import ( "testing" "reflect" smithy "github.com/aws/smithy-go" ) func TestAuthOptions(t *testing.T) { var ip smithy.Properties ip.Set("foo", "bar") var sp smithy.Properties sp.Set("foo", "bar") expected := []*Option{ &Option{ SchemeID: "fakeSchemeID", IdentityProperties: ip, SignerProperties: sp, }, } var m smithy.Properties SetAuthOptions(&m, expected) actual, _ := GetAuthOptions(&m) if !reflect.DeepEqual(expected, actual) { t.Errorf("Expect AuthOptions to be equivalent %v != %v", expected, actual) } }smithy-go-1.20.3/auth/scheme_id.go000066400000000000000000000006331463735525100167420ustar00rootroot00000000000000package auth // Anonymous const ( SchemeIDAnonymous = "smithy.api#noAuth" ) // HTTP auth schemes const ( SchemeIDHTTPBasic = "smithy.api#httpBasicAuth" SchemeIDHTTPDigest = "smithy.api#httpDigestAuth" SchemeIDHTTPBearer = "smithy.api#httpBearerAuth" SchemeIDHTTPAPIKey = "smithy.api#httpApiKeyAuth" ) // AWS auth schemes const ( SchemeIDSigV4 = "aws.auth#sigv4" SchemeIDSigV4A = "aws.auth#sigv4a" ) smithy-go-1.20.3/codegen/000077500000000000000000000000001463735525100151345ustar00rootroot00000000000000smithy-go-1.20.3/codegen/.gitignore000066400000000000000000000002621463735525100171240ustar00rootroot00000000000000# Eclipse .classpath .project .settings/ # Intellij .idea/ *.iml *.iws # Mac .DS_Store # Maven target/ **/dependency-reduced-pom.xml # Gradle /.gradle build/ */out/ */*/out/ smithy-go-1.20.3/codegen/README.md000066400000000000000000000016561463735525100164230ustar00rootroot00000000000000## Smithy Go Smithy code generators for Go. **WARNING: All interfaces are subject to change.** ## Setup > Note: These steps assume your current working directory is `smithy-go/codegen` (the directory that contains this README) 1. Install Java 17. If you have multiple versions of Java installed on OSX, use `export JAVA_HOME=`/usr/libexec/java_home -v 17``. **Java 14 is not compatible with Grade 5.x** 2. Install Go 1.17 (follow directions for your platform) 3. Use `./gradlew` to automatically install the correct gradle version. **`brew install gradle` will install Gradle 6.x which is not compatible.** 4. `./gradlew test` to run the basic tests. 5. `cd smithy-go-codegen-test; ../gradlew build` to run the codegen tests. > Note: since gradlew is a script within `smithy-go/codegen`, you need to use an appropriate relative path to access it from within the repo. ## License This project is licensed under the Apache-2.0 License. smithy-go-1.20.3/codegen/build.gradle.kts000066400000000000000000000201041463735525100202100ustar00rootroot00000000000000/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ plugins { `java-library` `maven-publish` signing checkstyle jacoco id("com.github.spotbugs") version "4.7.4" id("io.codearte.nexus-staging") version "0.30.0" } allprojects { group = "software.amazon.smithy.go" version = "0.1.0" } // The root project doesn't produce a JAR. tasks["jar"].enabled = false // Load the Sonatype user/password for use in publishing tasks. val sonatypeUser: String? by project val sonatypePassword: String? by project /* * Sonatype Staging Finalization * ==================================================== * * When publishing to Maven Central, we need to close the staging * repository and release the artifacts after they have been * validated. This configuration is for the root project because * it operates at the "group" level. */ if (sonatypeUser != null && sonatypePassword != null) { apply(plugin = "io.codearte.nexus-staging") nexusStaging { packageGroup = "software.amazon" stagingProfileId = "e789115b6c941" username = sonatypeUser password = sonatypePassword } } repositories { mavenLocal() mavenCentral() } subprojects { val subproject = this /* * Java * ==================================================== */ if (subproject.name != "smithy-go-codegen-test") { apply(plugin = "java-library") java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } } tasks.withType { options.encoding = "UTF-8" } // Use Junit5's test runner. tasks.withType { useJUnitPlatform() } // Apply junit 5 and hamcrest test dependencies to all java projects. dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:5.4.0") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.4.0") testImplementation("org.junit.jupiter:junit-jupiter-params:5.4.0") testImplementation("org.hamcrest:hamcrest:2.1") } // Reusable license copySpec val licenseSpec = copySpec { from("${project.rootDir}/LICENSE") from("${project.rootDir}/NOTICE") } // Set up tasks that build source and javadoc jars. tasks.register("sourcesJar") { metaInf.with(licenseSpec) from(sourceSets.main.get().allJava) archiveClassifier.set("sources") } tasks.register("javadocJar") { metaInf.with(licenseSpec) from(tasks.javadoc) archiveClassifier.set("javadoc") } // Configure jars to include license related info tasks.jar { metaInf.with(licenseSpec) inputs.property("moduleName", subproject.extra["moduleName"]) manifest { attributes["Automatic-Module-Name"] = subproject.extra["moduleName"] } } // Always run javadoc after build. tasks["build"].finalizedBy(tasks["javadoc"]) /* * Maven * ==================================================== */ apply(plugin = "maven-publish") apply(plugin = "signing") repositories { mavenLocal() mavenCentral() } publishing { repositories { mavenCentral { url = uri("https://aws.oss.sonatype.org/service/local/staging/deploy/maven2/") credentials { username = sonatypeUser password = sonatypePassword } } } publications { create("mavenJava") { from(components["java"]) // Ship the source and javadoc jars. artifact(tasks["sourcesJar"]) artifact(tasks["javadocJar"]) // Include extra information in the POMs. afterEvaluate { pom { name.set(subproject.extra["displayName"].toString()) description.set(subproject.description) url.set("https://github.com/awslabs/smithy") licenses { license { name.set("Apache License 2.0") url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") distribution.set("repo") } } developers { developer { id.set("smithy") name.set("Smithy") organization.set("Amazon Web Services") organizationUrl.set("https://aws.amazon.com") roles.add("developer") } } scm { url.set("https://github.com/awslabs/smithy.git") } } } } } } // Don't sign the artifacts if we didn't get a key and password to use. val signingKey: String? by project val signingPassword: String? by project if (signingKey != null && signingPassword != null) { signing { useInMemoryPgpKeys(signingKey, signingPassword) sign(publishing.publications["mavenJava"]) } } /* * CheckStyle * ==================================================== */ apply(plugin = "checkstyle") tasks["checkstyleTest"].enabled = false /* * Tests * ==================================================== * * Configure the running of tests. */ // Log on passed, skipped, and failed test events if the `-Plog-tests` property is set. if (project.hasProperty("log-tests")) { tasks.test { testLogging.events("passed", "skipped", "failed") } } /* * Code coverage * ==================================================== */ apply(plugin = "jacoco") // Always run the jacoco test report after testing. tasks["test"].finalizedBy(tasks["jacocoTestReport"]) // Configure jacoco to generate an HTML report. tasks.jacocoTestReport { reports { xml.isEnabled = false csv.isEnabled = false html.destination = file("$buildDir/reports/jacoco") } } /* * Spotbugs * ==================================================== */ apply(plugin = "com.github.spotbugs") // We don't need to lint tests. tasks["spotbugsTest"].enabled = false // Configure the bug filter for spotbugs. tasks.withType().configureEach { effort.set(com.github.spotbugs.snom.Effort.MAX) excludeFilter.set(file("${project.rootDir}/config/spotbugs/filter.xml")) reports.maybeCreate("xml").isEnabled = false reports.maybeCreate("html").isEnabled = true } } } smithy-go-1.20.3/codegen/config/000077500000000000000000000000001463735525100164015ustar00rootroot00000000000000smithy-go-1.20.3/codegen/config/checkstyle/000077500000000000000000000000001463735525100205375ustar00rootroot00000000000000smithy-go-1.20.3/codegen/config/checkstyle/checkstyle.xml000066400000000000000000000220741463735525100234240ustar00rootroot00000000000000 smithy-go-1.20.3/codegen/config/checkstyle/suppressions.xml000066400000000000000000000014721463735525100240420ustar00rootroot00000000000000 smithy-go-1.20.3/codegen/config/spotbugs/000077500000000000000000000000001463735525100202475ustar00rootroot00000000000000smithy-go-1.20.3/codegen/config/spotbugs/filter.xml000066400000000000000000000017111463735525100222560ustar00rootroot00000000000000 smithy-go-1.20.3/codegen/go.mod000066400000000000000000000000611463735525100162370ustar00rootroot00000000000000module github.com/aws/smithy-go/codegen go 1.19 smithy-go-1.20.3/codegen/gradle.properties000066400000000000000000000000571463735525100205120ustar00rootroot00000000000000smithyVersion=1.50.0 smithyGradleVersion=0.7.0 smithy-go-1.20.3/codegen/gradle/000077500000000000000000000000001463735525100163725ustar00rootroot00000000000000smithy-go-1.20.3/codegen/gradle/wrapper/000077500000000000000000000000001463735525100200525ustar00rootroot00000000000000smithy-go-1.20.3/codegen/gradle/wrapper/gradle-wrapper.jar000066400000000000000000001642201463735525100234710ustar00rootroot00000000000000PK A META-INF/PK Am±>=@?META-INF/MANIFEST.MFóMÌËLK-.Ñ K-*ÎÌϳR0Ô3àåòÌ-ÈIÍMÍ+I, ê†d–ä¤Z)¸%¦ä¤*„%¤ñrñrPK Aorg/PK A org/gradle/PK Aorg/gradle/wrapper/PK A•%Ó¦¹/org/gradle/wrapper/BootstrapMainStarter$1.classRËnÓ@=Ó¸u1¦„ôEy”@_IikÁ6ˆU‘@.,RuØLì!™ÊGã ü6 ±àø(ı ]t1ssÎÕ¹÷ÎÏ_ßxŠ½ó¸³ˆ»îá¾M|4ži£ÝsZ«}.à穸k£ÞŒ‡=eÏd/c¦ç‰ÌÎ¥Õeié0“c“ ”=<ÚO¦õ±b»í+7.Pÿ£ômïB%œÀÚßM• Ñ-°wźláƒ6i<øZ2³93Ööìèê³#ð‡Ò±w¹Úºl hÂãâòø¹æx8F×èE´‚v~ÿÄ—ê9à½P%pw8Ð.ÑR3êSò ѵ²ÜAcî+jŸgèQEoN SzéÝB£z÷±Œ•ªÄjÅ\Ã:­‡ÛØÀ zQ^ÅÁoPK Ai,«$ -org/gradle/wrapper/BootstrapMainStarter.classVÛSWÿsÙ°,ˆ1*‰‚±BI/µV°V(Ð-m´Ú.É!¬nvéf£Ðjï­½<÷·þ ö%Ø2µÓ—v¦“ãô;»IIb¼ÀÌÙ³ßõ÷]7ÿ>ýý1€£øIF2^à C˜”0%cïÊðA•0#aVF —d\Æ{2æǼŒd\ÁUq¼BNP®…p]ÂR2"¸!c7%|؉^|$A aQØÊ‹£ .aIÆ~ †¢„eeÊ4¹1´r™—‚§tSwN3øÉ+ þŒUà ÛUÝ䳕Ò"·çµEƒ(aÕÊkÆÍÖÅ{èw–u²‘T-»˜.ÚZÁàé»¶¶²Âíô9ËrʽÌhº™u4Ûáö8C ,® ‰ëê-펖64³˜Î:¶nÇ=Šn¥/èwñhv‘<ìl#Ì {.'­aénVfèð¸ÓšMèó–éðUÇ [µ´'bÔÓ0¹“^˜SXB¹D¨]ÃŽß.I¸üî,[†Xƒ€Í— žwÒ$w7'nm¥ž¼H«ÙSçI¾‹r•¿=£­¸btr6±šç+Žn™e ·¨:KºYPµŠ™_æ¶_¢%u­ÉPŒ-q iûõgôÅóºMÐ-{\f­Šç‚I‰jWË”0 `ƒ NãÊÂV8sÓÑKüØ «bâ¦åÄ Ê‡ÃãÎ2_t+¯c‹OŸ‹ëf\Ðɧ¾XâCƒå¡CÏ–“K‹·­„Û ”ò³ç9%¥9 ,¬HøX Ê©£ ‚; îŠc«Ö|‚OÜÃ}Ê.µwÊë¦TkÊ)¢à3|N­*Z‚RÛRU_àK_ákê¤zÒ3†U梾 ¾Á`\­4‹T ä[ßá‚ïñ€¦ì‡làˆ‚ð#ÃÐ+*0ô>¯iò¶X åìiFZ$K–]Òh²O&žÕÆñõêVoÑæ‰ÞÕFW¬€c-ÌM‘ßD²qh§Æ›ë:U“TŸ‘T…ñ–ÊÔ;#RäNv­ìðRózH$['tk=ÊeÍæ&Å›¬o²º§çh‰8ò6¿lsÜ•¯ØÂPý=ÒäÖ£ ðeîdÚ,±Xâî: º×¶Ø`»Ô¶Ùl"´zñϼ¤”žNòEë/¨›w¬Û¼¥-j=𒶨‘¨¨y1/ [Ö\ûf òUÚe÷“v¡S/7,¶ƒxBY|·šÍ™ZI,<ñJ¶ìG xâÏ&–¯Ó[šžŒžá °_é² Ct]b :OI Ó“a£$EÊì$úî>¶ÙCÿ w¾\Øïÿœo4ûÁ Hn"”#RGÎw¨ ¹ŠNb(Utm {ÌÿÛ£þ*z¢þðŽ*Âch ¼“¤#9_xW¶ŠÝcÁhyGè}ÏÕ¨ÿ/ô®CŽúC”A€ö» Sè¤s/A݇ô¡Ÿ~8Œ#Ž E¯Rü7q%нBÑß# ܤ@-8q;Dv˜{Kã0À*ŽÐÍG¶ÎÑ¢cäM%Ê8N¼›äãMœ „½UK•Ç;I¼1¢D°í ú%Ğↄ„ˆDvN¹˜Þ¦ŸDôí©Õâg¢ùèy|±Üpxïö©#Uô­c€ýëèÙÄþÜâU˜ý]£ý¿@õ}Ⱥ¥iˆP8T©(Õj@§ ö1*BŽ“Ü ºqg)øÔ—¾è“Ó_:#Bu¦}Ó™þ#þzÑÑqF=÷~t 8“ɹ7÷Üs¾ï»çìÝ'ýÀ+øÈDo4¢ÃÒŒH“5ÉœmÀ9çñf’¸ W.š¸„Ñ&ŒaÜ@ÎÀ[&¢x;raêc" i.˜4pÅÀCÄñ*á3t<¿’©ø¼ìˆÌªÏ——…Ÿ)(g–¡¡,Qá`8µ×Æ1oÕu<^¾ê{_T«»W…^öÅŠíÕªÛ{„_nÀÀò„Övíà<ÃåÔž ²o–!<ꕉksÁvÅ•ÚÒ‚ðgø‚C+-¯ÄYîÛò}1,ÚU†î1qƒ×œ`¿Ì VÞ¥qÔáÕª €sñ:þÙèh’庯ð V]änE”ZS…›|…g\d®Mç³R“ÁËe™€!ö´›!Zò\J„[ éd'Ì­ä2c”J·&ùr]qRru·JÁ+¢èå8 ÔS›Xm¥šï“çÙšÉàMxÛËä§Æo—Är`{.±h§“-Õê’gÂT^*wSàÜ٢ߡ¸µd½ÅälÉ2º•gPZèÂU m8l¡Ž ÓÂ) «AïX˜FÑ œ´è¹yÕÀŒ…k˜50'=ïÒ±îA›áˆZuèè3Ó57°—ĶS"ÎxÏÂû¸ná\7ð!CöTDïàM-Ü%:þçho-%+Ô±ïÓBÕfuN×I¡Q{ùEñqM¸%‘íÛ帨vËÆÈ>E¯ø¶[¡Æ[áNMLݠʧò»77PPb¯dÄ15º/Nûnj3‹¾·ªRZlÇ1ÉeShKü¶ì¡|^ö¶dSÙ:83ôÐm˜ñ…†Ù1týµÈ¦¡1$û†Æ0ù»p”ì1ú·@ë‰ô:Xº ¡ôÀ´´¶†ðÕM¶ :Ùï)ö4âGÄð!ýLx ŸŠG/Ž«ë6¡p™šIäÍ©7ëxŸ#B_`¤§ñî§8”þÚw0Ó=máuèdïÞC”C-‘¯`ÌI÷·ˆË¡ŸœÑd¥)VÇ`ý….û_ ë7R÷;!ýü‰aü¥Ø¥‰3¡m³ÁKH)v#è#oHÍúÕ¾šëÐñVZ¦ç«NùIr.n¦»{×Ñ7Å!=©ßÃIbNêŸá¨äœÔ7`íø‰‡h a.Ôéï£yDÿÑù}±Çø´ê2^@Ù#G£æe! ±†YY¦cœJÊ"#òª#KïµÓxY&·-/§D15“¢4%ªÚÐ%V颢®kšTÉèD¼¬õÞé¾ã%Ð|ë“ Ú@ËÛ¥ÈlÌܧÔgêh ¼¦Ðˆ¹Š|/ª7/]>(Bó_PK Až‚­Û¡~4org/gradle/wrapper/Download$ProxyAuthenticator.class•UÝsSEÿíMèMn74ÄJ©X©1IÛ$Eü E ¤ `úa¿ ´Û›ôâõÞxscé?ä3:š¢Ì8>é Ì8€:>øWøàÃÙÛ´“¤;éî9»¿söœß9»÷ÑóŸ~p%G1¥á´>¼«Ô3Q¼‡÷•ôqÖÀÆuœ3U€(>Šà¼š/tã"&t\Š Á¤ŽËzpÅ@+oŸè¸ªãC—¿nU“y†¢ë•seO”l™ÛðD¥"½Ü¤»áØ®(ðœåXþy†ÁTGdz‰!\pK’¡§h9rºöåšôÄšM+‰¢k {Ix–Ò‹aíÍzî½Í‰š¿.ß2…ïz üªãH¯`‹jU&ßéèä^wYú³d¾áz¥¦=ËuN¦ÒÅ»âk‘s¤ŸÛDbåwg[%ØØÂ)çæ}ÏrÊŠÈwM×fˆøÅª¤øcó¾0¿˜• WŸ2\èÌ_Ç Gzµ{y5Œ2œ8Í`Ì»5Ï”W,Åtlg+«2àx Ç9Ž¡_IEŽ)Ìè˜åø sóXбȱ„ë:np,ã&C_{æ—j–]’Ç Vé°ìnêŸ+Ã[·q‡!ÞnÈp8ÛÆ+(ŸªÁ!°¦¢59N"ÉýýÀpt÷„¶ŽL2„RŠú8uÓœüª&«d´°YQ¤¶é©æ¦j9$Ù‚¤â$_ÇphvnæÆ2é—õÛ%Q½8W¤¥æ¨h…P¼YgèVe·‰{wð­m~¼KTaš²ZMŽæ©A³¼Ûîk¾eçè~¨F!r3’Ú{ôÞh­§îžïî4U¬Å)U’"âšάݕ¦ßâ°±Døna]xž'6éaJ¥W Ôôû„³RH/aÞá>z«Y<®nI!hêRQ'¿NÚÒ4šÌÐÐ2[}Kš†»<Æ4ò@6poÑVÝðð/iašk™:ÂßáÐ7°HêªC/f¾‡ö‘‡ˆ.oÁª£;Ái¨#ö#kø=SÿblX¼6=òºG¦"±<ÇZÞ³ôÛJâDI²8^I Ø`ˆ+ñ’Xv‚Bx±žm%²$¤§,”%”¦{X ´,¥t¡„¥ÐÐ9Á@ - {éltöé´)3̾vhëþ÷¾'Y²…Iç›ï›|ñ}÷ž{î¹çžóŸsîµ_ÿÍs/Ñ*þQ)ÕÑxè?é¿<èý·h~©ÐÿxÈExèqú•—~M¿ñÒc4†Iaö’—KOáóÅ÷Ñ,ÍRÑ,óЛ¼ÜÃÕ¼B45 ת\ç¡Õ\ï"ñ¯‚ 7zèu¾På•^ʼnE«eµÂ+¼ÆCWÒ*¯Å—/Í¥*7‰¯CHjVø2¡éå¢iÍ¢¹R4ëDÓ*š6…×+Ü®òmáf¡ûU*wx(ÂA•7ª¼IåNAífíöÐuÜ£òfý”·¨Ü«pHúTîWy«B'Þæá«y@åíBÆ…¯Qx§Êת¼KåëTÖUÞ-t»‡U6TRyXå•#*ïQy¯XUxTᓌŌd[TO¥ŒÓ¹ë!=5×Ç÷Ç¢q=¼9N©Tg$e`dòtàິ9bÄÌÈ nÆA,ïïÞÔݳ­{×ÖöÞP°§l{ô}zcT 7†Ìd$6ÜÌTÖ¥L=fnÕ£ihgò¶öoØÐÞ»+ÜÞÎÄAQ51ÍÚÜÛsUo{(´«­²ÂcЮ­§»»½­;ìê vµ÷ô÷íê vvCí˜YÂâåLþÞöuë‹20¹£ñáaqŽ…ñäpãpRGÆýI=‘0’rš*w룓½­F2‰ÃZ‰iö¨/&çÃÌÉ©ƒèbŒfD½Ü²UÚŒD»ô˜JC‘á˜n¦“PàâÂÙ˦v:¥2Ü—Eb³…©µz†£ž´[™œmñ°!TÄŒîôèn#Ù§ïŽÂÑñA=ºUOFÄØ&:Í‘vÎL¶Šóã±}°BhšI–Wçzr¢yÅ4S¹ÃäA|Û­ïÜBŽ%’ >O"O|Uqé0¼ø Ž‚cLdÖ\L~¾Szvï1Íæéᔲ©îÅ:)Má„Â×+ uÝÏÚû7Ïèôß²g³ã¯À<òÿ¨ÀYZI¨¹Š #Ò¦æ5ðŽjÁ¤†mÝ‚l?Å ³±¿7hËÄ7D¢†”¨èá°ÐI¬É.l¤ÌHÌ–=«p- Ú~`ÐHˆI,®ÈF2¦G± 2’ë† ;­»ÓCC" •ìhÅÆ±ôh¯!”ôšqSv±as$O’Î’x#B9›ËÚâi±¢¶€^óóÕîDÊŽ½P 26=or:Ük˜}‘Q#ž6sʃÏq.ÊpÀž´™H›0¼¡bº$Ë*fƒ±üI%¥ýÉèûu¢à¦6N+¼Oáý È1G˜OqÌŠ©Æ?§€:3õ:§…˜*x¯UOE§"aEQÇO7’€€g07x„ׂ±!;m·ž2Ö¬n Êdº¬ºH®-V7+† ÓZ”ì2Ì‘8\¼ /i E±[£5~ÍÌY^Å& «²•`fA»¸'Ù®'£Á57+ßB’qm£[¢ yy+ä]¢SNÀeÈÈ•H¤ƒé¨ný9Kœ_Ü[…Ç––‹É*¬& t<‰Óø ÄÉp`šS]T‚Gж±°¸¤x­]Ê+'§r4wŒ+Ý ·¡UÖ À¯x“Ù4%Ñj|šÏØ€Î_»$·b–•ýûâ–§í}òk€Â¯jü¿ûõ÷m¨¿Dã7øÍ¾ìÒÙE S@P46ìŽÄ ëuS7qcn³n÷V 0-ÕZq7õùP+b×T`Ä™Áo‘T 7©t"Ç6áh‘`·vÁî>+4ìдòMU!q³]V¾ßÒømè²N4쳫†fìjPn G')J<Õ`)zd]Ö…KSKSꥩfùE^Wãò;HWE2Ip}îÒyÎLIr%®e(-ei§Ÿáë>§Ø#¾v:ÌrM¹¶ÁtÅo<ù9,–ÇžŸ:  Ó7’Œï·^Ss‹Þ ±'‡8  _éùÕùE¶à§Ê7aÈÀÅ ²€ ¤æSI5’3PU{ÖpâV:¢§º¦|lÇ]9&…÷ŽÜ•Ê ]7À¸jæÂN;·;âz|Iõô¥Ó)Eåoú?{\‰ëªcXXaYuŠn~ÅÌWŸ™&—¬”N"iÊï‘<)ôoˆ›¼;bŸ”!á 'A´@[‘õÄä{Ê=º7I*.3.1˜ãÉ>)>¦¿àü“´‚‡‹˜›˜ÅòÁ¿`ŠÌ‚÷’çé5®OãÙgôàäï6>ú—.•Xn˳k8€W”zHÉz8GŸ{Äß¼<ƒ¾°Äsb3û ¨ƒ”ÏGWõŽV1¬(Y9U†ú(lžâߢŠ‹¤‚“—„y±—NQû;@R²•8„ú’Nµ@š>{ÊcbãF!Ûµ?‰¸Fäâ4Ai3ÿ‡Æ #F]ƒÑxJ<¬Åd ¢ëÏê‰fßžš§ð ¢©ü53c!—)¥µJá·Ðàˆ!j‚~G<*ßI‰³ÂÞtJð·Lhíþõhj ²¶Ùn)+“¿ðd¾±$±Ù‚vAuðOg³œ0’ˆ-néGx/»0lö±|e‘5;¦­É—2íù=ÿÃæ`ŒHl_|/üwicì˜;¹, ÖzД¿È,vBñË EÕm3áN bò5îŠ'GusŠVEÿ¡ˆn¦ó¨Ž bº<ä§[èú·Q }œÐmô‰Üø0ÆŸÌ ãOç?ƒñg Æ>ñTEß'^«ò{»ýŃS~ñvÿÝ’ÿÒп—¾ˆöK ì 79ñ-¯©åºúTr‚cä|F2߇փ/ÑE4‹VÓýèÕXìô=(ÿZT.·gÙ ”ÈžPÁµx©ƒ‚رX¹yœ\cäî¬Ér’T¦®º“TʾÓKäí®­?IãáZ†Î,¦3xÆ%Í€z’Ê´íèÄ™Úc锚V‘øûÕZRéZJ—Ò&j¢nj–Z°ûRZF_¡¯¢-èkôu|Uê oHó<"%”Ø\ß—5wæÅ\9M>…Ž*ôØ­ §B[#…¼T"»‘ ,*œpXøÿKÌY>xBzàIË08 > ª«£ŠšÚãTYSœ|5~çqš]ãw'®§9µuc4÷8UmžpCú|H%j ® RºnZGs¨(i£å´žj©] ÅÚ#綺œÛê$NJdïˆt›è :eO Ç%Í£ÙüwÁ(.û,ßÂÏSô´&kÏŠšg‰}óO’¿®«”£rtšÊÇiá@ žÁ‹NÐ9“™%7î E´‘ª©S*mm¸ÂVúQéé’E^P¿MÇlóõ“C®,¯ËÐâ »­F*0 `Kpì´ý-98‚øúŽ âïÒ³Xy\nÆ´ýLv3ÇXQü±mws“«ö—¡ó›ÜãtÁÀ8-ÀÆKÇhY·ß¡åMΚZ¿sŒªá°MŠß —*­ryE†êíoƒß™¡Æ&We5=¯6©btáOéZoéÚ2¿Ë¯fhåÀšÒ’‡iç Z•¡‹î§²qZ=à»xŒÖœªòÎ-=¬¯õV•ÉoYUÙZ;ZzÏC¤aÊsøžh¤kËà„ªZЪ¼'éR7öWsK3ÔttâALUúš3t™ßušT¿+C—ן¦žú µ¥Ž&El8NW"l×ùZ3Ô&ηÞ׎^†6ø•1ºêTSyq!Šâ/?õ ,>‹Þ¦÷S|¹ö}Ÿg˯€xÂ%´ èº\Ûi1íD@î殣U¤#˜w׃í0õm4Œ™P"4_™´—n¢(²f¨NÀgIÄY >5é{´ûßHï‚ãÏèfú þsdÑ÷é ý#ÝMnäEtŸG7ó2:Ä5t+7Ðm¼ÙV`æ>`cÚÖ)Ø9M'ù »\KϧšÄh=ôÙKÏÓ ˆÿ!h÷"ze8Ç:…äæÄnÐËÐGHÊb=ƒèY,á éûôr@¶×º¸ž^A¹—@â«@¤ŒwÈ;ÍʪS¨C¡×z]¡7zS¡·d ;ÑÿL\,¹.¨š€òJQ>”” œ±`’ì)V芰BoO Uc°ÿÍùa{äE *~wig˜›`g‘«êÇ©c%$Èh6âg~DMéÂO÷õë§-¾ÞÚ1 ’¡@Þ\mnå¤Ð;¥ƒEö?§~™~$ùëé÷à+︉u«KÄï<íÌóœ RdK öèë®?ƒ²é믅NÚúi2 ¶! ê|Wg£g1SS?FÛ­ØA:˜Ì€‹ PÜ(Îìà…Vó@w9êèdõmÉ%®–\âj¡?@J°úú#™Æ…Þ0Ö¯È ÏÎÉÙs5˜Þ¥[öä(6•á̶#C×tÕùvfèZß.Ç t]†ôî:ßn1tb<àð…C 69ëóF††š\~§ß…IcÀQëÎІ^¢È1¬Ü+VF Wú\Œ»É)Ý5*2&üU"Óõ8¿¡z ¾–j`BH«°þ|”T‚K‘ž·À½›Î-8ê­HÚÂ^×ÀóÁñÇô'ЀÀûSôœ ÌGøsxp6¶h·¢Þüý%z½H4…žÀÈ‘œ­H»â†‰KÚO诡£°p-•L`{‡„1è§"öL=Î IÅ’Ÿ!çXH•S8³MVǘ¬Ž°D—ƳÃîºÓt.òèBA¨rmMJÔ[:¼],‡!ëô Š4Úv‡»9œ×ûñ°ÂÀƒç¡O˜Äª.r%ãÐJ[™ÍM Ç„ñ,Èä]Š‚­8Ÿökߟ_ÃPU:â]š3aºý©G­ÍÆ©±\°^¶&a¡t"-ãœE­eY²ÿÂè›Ë›¼8\3ެK tÐŽBNûÐkØûPK A!9|¶„ 3org/gradle/wrapper/ExclusiveFileAccessManager.class•WÛsWÿYÒÚ«u|iªVÍMNZ*;±•JŠlB×NåKœÆŽ[—t-دvÅjÇÜSšB[(·¶ÐP`x¡ 3íL«$x†ô©Ì0à }á…0¿³+9¾%L=öÙs¾óÝ¿ß÷íúOÿýýÁ¯t<ŠgZ ãYµÌjxNG_ÔpNGϨ×Ô Íx^mMEœSKA-EµH ›1¯¶ ::`éÔvIÃb3lIÅžDIGŽºqu”ñåfxJ_¥~O¡ªc—u,áJ ê§íÄñ–ñ _Õð5öñÉ¡± #ùñá SgGFòÏ tŽ_2/›YÛtæ³S¾g9óý­C®SñMÇŸ1íªhñ­’t«þDE@äv”]ÛÎ;¾ô.›¶"Æ,Çò Ä2ù|÷Œ@tÈ-R°mÜrä©jiNzÓæœ-•=·`Ú3¦g©sõ,jÉŽ»Þ|vÞ3‹¶Ì.yf¹,½ìð•‚]­X—åˆeËÁBAV*¦cÎKŽÆÍ€ 0˜ ã°Ü¬âëOUß²³×)T=O:~vÈ´me±¿{]Ô“s—dÁWÊ䕲å-3ÄQºd»…EÝ!£C½…Óq¤] 0ŠE•(¹Þ;&f£*2³B5ûþ?ÍÊ^¨¤£±}Òò虫\j÷L§è–ÂøC¶‡ÖlÙtGuZÝÛ5Ó[" äMn,Ér¹Q–‡ïíõÀôtÿ1•„)ß,,N˜å@LÃ×5|CÃ75|K@gùdÙ·ˆ'ÂhÊšwL¿êQ÷ÄÀtnkŽ}¢2†ts¥óÒŸ&J',ÛVXjÊt³Š%syNÙnE>]µ¤o3•É;6‚‹ÄlK¡q¸oF3åV½B½ÖûîŽÌ>%l W Ä!½8$`¬ÍÀ ø¶À›»ïDÕ²‹Ò3ð"®x ßèýD]AÌõÙ…EßU úð²Wðªï©åûx•À¹cóLÕQ­½V$ÃCnÕ.¦×O#€&¤A7zxŠ N5@¸ïT3„Ï(ïúåz˜§Yò4©}Ï ˆž] Ò³û4½?ˆE¹vqÝÀ<Ì ŠáÓøLø¥˜ÁNŠpÿ8>K.š‰ü1´â‰DgÖ[A|ö4n›khéLp©Á¸ÖS½¼ÍESÑv\ÇcêÙöêÙ~]+蘭ËvvRˆÜ÷5dwÞ¹˜ÈÅÊý=ï#rÍ<ª‰\KªåT´¤öúÛ8º‚äloç7ð`.–ŠÕÊÅSñ WÿšŠßÄ®TŒ U4]»‰Ýï¬~¨Tô®7½‡6•‰övîãþà7ÝBZ §§Hè Ò«@'¼c ¥¿›KlÏ“¸3Ö1·™ÿ¿ãcÑÆ¶‰õDQDDwp>¨žäPõzp}”ÕÊ@8B gmγ*& _$W™Ô ëö+ôkôŽrÖ}¿F¿A?~‹Ô(µ‚AZÆÇ8‰ðÃñŸü:üưŠqÁ¤Ð8­ Ó›“"‰¼Ø…Q±câï?… z5)“çqj…™sDP·hí hô F›9Òˆ‹ޏë§uì>cÄÑ~êþiMȈvz5ÀHMü¾ ªŠø †ð$?b‡)n¬IŽPò$)SÐþƒã¢üݳŠhÑ ǧB¢†¤†<£À¿q¢ÏÒ0¶Ê.Ò¶åƒÃx½dÐ(§øÇWW½§Žw@óMì߉¥ß­5S< Ÿ ’a„<õÀuõ.«ËŸç3ôäGhí¹…GÞAtü=’£dl¥Ù¦@]± …ùßéçysŽýùü:õmŒšO• ‘ ²¼8x4ó?PK Aì„,y†-org/gradle/wrapper/GradleUserHomeLookup.classSÛNÛ@= $vŒÔ”K J)$´ÄÚÒ E¢`@"\”@¤Õ-Þsc-ÂÄg¥ØOˆN™QMž} (1ŠáìÁ#Šj€ RŽ`뛿Í€i­õm¹õßP/ÊQ«ðWM:‡Ü‰â;"¢¬+) Q¤g4·óæÍ+ÿ{óÞ >ñÐekÅ"ÏT±ÏR¡âÙrxŽž«ày*J$¹ÏWp—‚øñB?^$ /Vñ¼Ôs~¼LÁËUTIÆ*¼B.¾RÅ«p^Á«UÌqɯQñZ¼Nн[÷Èáõ*îżÑ7ùñfoQðVux›ŠZ¼]Á;T,•îÅ;ÜçÇ»T„ðnïÁýòë½rxŸÞïÇü¸ ¢ÈùU|ªø0R*F1¦â"R0®à#*6á’Š;pÙ+rÃGýxXÁ#òócrñã*>O–áSø´‚ÏHÎG¥H’?‹ÏÉáó*¾€/ªø¾¬à+*vâ«swìki ·éínßw¤£kWû‘®==]»áãú)½1ªÇ‡»m+j˜Ùjƶ·÷ëѤ!,ØÞÖÞÓÒnoËÈ©N³ìíílïÉPæP 6•lŽÄ#ö¢Pý~_«9@máHÜØŒ3¬ýXÔFšýzt¿nEä D*ämpn˜rg2ⵡâ$¿‚¯ ¬ MÞS·NkÓõb©v›I«ßpó|NÁA[-2Eå­š‰Zx•ÊÇ´0Íœ×-N7J3s¦àD²ç^ž²ìÖå}Q³Œ‘¨.oV‚ü›BÓŠµ—ÌåÓÛJT*&©Nåu]§Ó6‹6«G›é> ê&ŒO0¤VÄ>Û˜ÏÓRÓâ#€3øÛPSX—Âú6¤póm÷afx›úVŒâ–+TÑhNaó=˜'‰[}—°¥¯¨¡{ [Gñ¤+„ªå"¶ \§ÍjEûGrSh#nÅv"×J´¤mË"в ŸR5h!Rˆ‰q+mÜÁdè nÒö†¯‚û’8ÅY5wžfŠ ®kü:˯…L8wõ©ü«‚oŸ‚£TÁÁN…JUùM;ÜçdPÛ(KaûݘåX,';Rèf¡SÂX”tƒj1ŸAž„³ÖÉDWiø¬QåC7­fSZMù8vRzUâ¶üèD³Ä•§Å <ÃáºóPK AúâæªÛ"org/gradle/wrapper/IDownload.classEÁ Â0 †ÿÌéæ¼ ^õbÁë®* AÑ{ÝÊØ(í¨Óùl|JìTf $_òçÏóuX ï¡ë! ø‰®”ÔÌai'$|TÆ:Ü/Ó ?&áãNJø„@ÅÕXaXQÕГ7é†&° ã°zDèV„çÊ™&¦§œ6ÝX”›Ùú`ÚÑ-³×6¦˜L͉ôv·ÓeLµÌCz~@Få@º«Yá%üHÁ™ë'ø©„Ÿ)ø9~¡à<~©àWxLÁãøµ‚ß°þß"N˜iÓ ÍavÂyܲ‘ 'ð; ¿WðüQÁ“ø“‚?ã/D\˜„¨V+mĪMË©N›·ëÉjJ jÇ¢N4 „º55e™-$õ¯|à¿áï žÂ¤ÀÒiïu§MGOhSBÙWt„ìäå³%+i/ÆŒÀ•³‚FàúKLÑ\г¥‹Êva:PÑ™^hw4*\–ó’kQž—–^\)¡6jkªãæ®À†’•¶dœQŸ|5\$çÇ5g—ʧöò@¢y—šÐгpú6`Ù&•‘ÆšewÆRæÄr,oIàÚšRe¿¸~—ë©¶Ð\s©nõ”gçB²së`Ê2ÒŽ¶Ku†È1j4ª¥è¢ZIWU|ÎWPá1æP™³j›YmÃlj 9ù49ÞUÌ[:–¥y‹c™‡ìžc)G£«Ü§Ùvþ±‹NæÐù45‘gõjÖ¼yΚ !±¨„BR’gáyq)¤P€ƒ1.(Ìe%ÔOßWßU½¶N·qMá¡¶øm”;ÖšguhéwXñ¸ Öpg/˜ÙÀ¾¨0Š„aŧM[˦mš»ií9‡l¼H›vX(65¼ºÈc…Àá „(÷)ß÷Xô²,JË|ˆ®{v³‹˜g>BúT᫪ÉßÏD¢‘†ÔT—v”j›ßt»œ7°s1` tù.U½z’ÿÊ{ÒÍ„×z¶e÷%Ø’øK)y9XÁÆÿû튢u’,5N V>]¤æêE¾#¼+§Ká%TÓ—N È>+*ø)P¿%ÛÓƒÈíéMäöô,r{z7Ñ7¢~;ð|ân§ÑÊ Q]]ýD]ÃÊêÇà« ûÇ௠ƨ Ǭ‡ôQ–áfjƒÄ¬Ä-ÔVÑI@ºwÄšèAÁOÚ#=¾[`=eòY„úëDYóÎBîG¹7S2˜_¹€š ŽÓ»—V}\6ŠõõTŽ¢™/z‹&pygÃ"D]Õ©ÓXBý„‰~iŸ'´ò>Hþðû6ÓÜ?e]ËG1<« ˧Lhñ‡ýYQbWfI+¯"‚Æ ®&¢ ª½ÍkX|ØÏñ!ì÷¤]Û—³x+ZÜÝç ´Â3¸N`›yt½ çe¨%X¤¤†”„ƒtLÏš¬¢ppµ}'þ;™•È .ƒzÖG?ÚlÈn”yjOà¶–@‘è‰ô,dá•+½©/;m.P˜Ç¶Š-"…«§œ>òG=„ç†d©k-«&°†â±¶Ïœ:‹uý<ºb7LBˆƒ"!’ô1ïw1u7®qÿ‡QƒÕ„ï5„§µè¡û}4$¼Ç ß#„òã„ï; Ý' ß§ ݾ#Ì=I¨~íb nëq‹Ø†±]â v‹ö’¦na£GÜ=âzÅ#Ø'Σ_<ñö‹‸€[]L‚Lú—a7ö‚ORNõ’v?É®Ç^IÒ~ô‘mFuû5âqôÓ®Àˆ¸—rk?ñ÷àYìÃÓ¢/ÀA’r ®äi¢÷ön#O¨´Ò ùöIIè~ æßHv9BÂà,–•{­‡%4ý þQA#6? :T±•Ò.î¥"n‚r8ƒõ§Jf¯âdO0%†Ú!—^Ça·†áî%`¢“F•´¶Þ_7­ö@þPK Aƒgh•|-org/gradle/wrapper/Install$InstallCheck.class•”]OA†ßi·]ºl ÖŠT)Ùò¡ QŒ‘ÄhRôÓï¦Û¡,,-ÙmÕä šH4šxé…?ÊxfvûI mšž™³;ïsÞ³3»þþø` Ä‘—a9AaE†2]ÕQãšÖul0UW\ñ²~"RÅ#þž[NÝzá¸â1]8àŽÛôÄ®ð}^¥é`…ËkUk¯á9µ*­ÒÃU 櫚ßà®»s(ìc•Ö„·ãrß>Ãvî¢z©X÷ªV`ÂúàñÓSáY!%ÛM£:ÚN½BE&ŠNM¼nž”…÷–—]åªns·Ä=GæáEý¤eZ÷›¶M ÃV®·ÃQªÇŸ85§ñ”a¡2 ©™m:Tq” †ã—„ç8¢ÂÍ-½cHî5¸}¼ËOæ ®:É® +¹¡á’ùXèÖ%`k@ÿÑh6$h{dPï oµ¥újgʤ±Woz¶e;lUBLÜÀ”‰®˜0‘4a`ÜÄmÜѱɰ8¤#7qK†‡ “ooÊGÂn0Lÿ£6©„ ½Q zñ"HK4KK;4&('w JÙ&4šÆOÄ÷YþôÏJu•b\ÞaS¸Fs3X…ëŠaÈ&òla¦‡¸‰ÈCÂ>eQ'¨üX~ùZ~åÑ/mLšJ€ÍBcsH° Rl^!3 Ó˜ÔL™šÉf#ô£'–yN£¼—Ì…öºö ZôŒ.D»¼f»¼&Cð,ýcˆŒ?c“c˜£ªÏ y1²;ëë9Ýʼna^rH| Äd¦ÿ gqo€8:œø~{Ç;âïˆ\&N*ñb{³»Åì2q°Ï9—0©vŸ>Y¡úPK A^,ã¡C- org/gradle/wrapper/Install.class­Y |TÕÕ?'³¼ÉðØ"! ›#ÈJØ—@±I –Zõ%yIFfóÍ“ÐE[—¶ÖÖÒŠ]l­•¶¢•ME­ÕZ·ÖnvÕÚ}Ón_[Zú?÷ÍL&ÉCÀßÇ/ÜûÞ½çž{–ÿ9÷¼;Oý÷ÁcD´˜ÿ¤½\$ÍYÒL‘¦Xš©…hJ¤™&¯¡ •ótgxˆ¿óH‚À 2³N¤oÂõA*áó¼Aú&7i_ qsVa; n’¦EšÍÂg‹,}K€·ÊH«ÆÛ4ÞàÞ)o R=·I³+H?æÝ¿Mã·Ëã…Aº€/ ÒF¾Xc#H›…ûFnp‡¼tJ³Rc3HÛ¹Kãî ÷pDšK‚´SFöi7OpTú˜4+ƒçD€“A¾”­BN±-*¥|™ô½"PŸÆýAê«öðÞqô7~‡4ïÔø]AŠñfiÞ-„—k|…Æï ’%´—ò{eÁ•¢ìUAú»¸¡Gš¿óÕïÓø&½)ž²h´¡ÇìØ£^ã¦Õ5R)3ÅZ׸¾n{ó¶‹Ö5µnÛÚT¿}[Óæ–‹¶ÔmÛÀTÔ|‰q™Q5âÝ5­¶‰w¯fß~q{‡M›¾–ÉMtw›ÓŒæ„Õ]ÓmQ³¦×2’IÓªiV“XèLôÆ£ £“i¶aӺ̼ì’4ìž:ˆk ç9n ¶äÓ`Ñt³¯#šNE.3×G¢f]G‡™Jm2→­ÆCãI€M$±×2]Xö:z½¾&§»|“·!Ñi2MlŽÄÍ–t¬Ý´¶˜$:ŒèÊÈ{fÐk÷D๙®;;¾†ôÁË4ls]$e3­rÕ`§Óá]‘î´eØ‘D|u¹ãôH¢Fl"®èÈŸg*?mVШÛ[‘ö´¼o·¢LöqÓ®Ù¾µ $Åù$­=ÆâeË[Ó1¦ÉQQ}]Þ¤Æ×a¸yô0ÓÒSZyî˜UØY“×E¬œPÃ:ëjó]‘¤¼Ã–À‰™”e)¯‡[0Ù‘ŽÂ¼yÏ)É¥Ü-z ˆ›€­ötW—À²`w=“§ÇìÃx¾íRÄ$³)³#mEìþšM@' ¹.Òm¦lðòt Šs»6Å“i;™FL¢­½Þ7lƒ©ì ˜¦‘ª^I"ŽnµŽ=›Œ¤Â™ÆïÓøýÝ¡ñ˜¦^fZ‘®þ| nM$€¬ Fi=ViW¼dp:7?7A„’|0¬3SV$éøØÛ± í$‡?æ£5Íe† ÃzC"ÃM0ýÉlЄG-\3R쵫5¾aXÍLø*±RâÜeec;¥âˆl-æ8 N0:ì´UP)L%ÒV‡©bœÙ—4;l³SÍ¢&¡jØOùªû- ¸{$©FOb“ÖHwܰÓ6_x ^c¬ ¤L»±سŀ[L+I¥$ƒ£¹AÑÁâ´Ü¸*³s«it:É4ew&Òv¯jëõ +fض"ðF‘üÀ'ÙŽDžgÌ-VBÒr}:u8q’鬱2cŽ‡Í¹¨]ó¿ZÑÇ–V: £ç“Œw€ÔˆÅŒ8âOQÂÊDÆ„I#©‘8Ž1èê)+ßµ©C€WØiFMI·–‹}@¨¥Òêx”x tô@˰ÏÚí–'<€»F¿„‹Óñ½h|¶kœå{ÀgÆm«?çe`,­Ak”Ikð@.GH°d™lΗÌ(¬"‚¬’_Éί/ÂóKSóà 3Ž'ì0Ê\ÛˆÄÃF¼dÎä„äÀáÆÌ¶á.¤ÿ°Ù‡³<Ú^”£ë_€ÒdX¸Íí—`XçûùˆÎ<¨óQ¾›éí'"³w [$±7&Îðƒéüe~ˆiã©5×–0ÍévYÎÇøa>C·éü?ªóW`RîÙ!µR¤Ceñp¢Ëmi¸Ë€s;Ï)—ÆÛnWš˜Ñî1.3Ãí¦ÛF,)•C¸7b÷,(«O +¶{ žïúÁ0?œ´Xh÷‡¡G¿ìå@´:Ñ™y4,eS8’‚Ú–X.ï”%aÂh™<t©í2ËÃ]V"ûØV:%žp*¸¢X8¿*£¨« —¦JãÙ„–’T¦œáœ/;2Åe­ò Ø„ëT…8fBçÇøfEÃøÚšŽÛ‘˜™+^$辊Rb8ûÖY–Ñ/)XçÇéEŸà¯éü¤<}¤ŸHLÛ#ñL ƒ§P:œ¬ìîì§„¯£'–@äY±l™ÎO#ÖÀåŸåçtþ†~ª{9Èr9¼9¿ÉÏ £oIóíJåÊE¿#ñŠ=%ʾ›%Yöéü=!›ýú•ŸÆ/èü}þÎ?”ÜY26ÇdLPÑHG;Uä 6F f‹ãpr¸:w%¬Ú°äŸiüc"ŽÑ©q”‰Ã/ñO5~YçŸñÏuþ£ÆÓz¢Rç_ñ¯uþ ÿu¬k¥Ó«ü;/Žü¿’µð˜ÚIçW%õýQÜW®Êy“iÆë|5çã$¿œÌã´­ÇJô:ߌFV3(ìÇÄÇGåUµLSÜJQõÙ µzYS“ôns»JA ÜmÚ#µŸTV>ú.eÚ(¢¼Û‰)e®÷áSÙé¢3¹;zC1E£ÄVQ9õ†oe‚ ËÝÉ\õ† µ‘V;måÜ+H¿‘ù¬«sý@S¸Àé 0âkÏ®i€p®·CN¹ÇAA¥A¼.r¹s(?Å(ë|í®/—k¦t²Ó°¥ö€9_G4‘ˆ¿3Þ²r¹ƒš<2Kª;ÓqvbÃðÕQqY“+†üøžê¶{¶eO±|?Ï(kKŸ»mª:¹vnä;‘•C‹¤cIŸºþ.u"¨/§1Ìvµ°«ø¹š}‘Ôìorëô¿ï¼©È^X×OޱXÎÏna_ŸHØ#¹ …X«mX¶x`¢”{ÍF:Ž¢ÀÚh`d¶ûUÎp”äôYœù¦E~úºøÍKQHŽ+s±¥Ü~8—NvªkO%¢iÛ”xµ$?^Ì>'ãÊM×zçþj2 8FHj]¶¶…=N êÜS_6b‹I¦Áˆ'â¨M£Ž4SË\nYT4¤ÄÒ"n¾£†¯”´^#b£îÀa b3/“—ä%¨‘W¯ÓÜ/'d¿³†§2Wc2¨mV÷_kÎÉcnÑ$EnÊ^9æÕl8ÕTbŸIDÙ÷‘Ç‚3ºZ.º²…’úŠ=­@ÍÞÏ…NzM!W/‰î‘µd?ªé˜“ù¶d x¦y§µåê,'ç„–ƒE.··æ~¹Ê›X­²Ys¢iÝü—Ý#ŸÆu hömî: ¦›ÔÕeÊÁë]^¨,ß[#oÑ&ö©M ËlŒš1¸±0.nöÙ™×ÑþÉ% ærîý±=ÎÍxhYc.¿jòuqÇv2ûzq¼˜tí¥r"òQˆÞM—Óx+ ÷P‘Ü9à¹H. T¿®Qýµtú³@{=½íûñv8xÑÏ­¤ÂŠ! ¶›LO’~„ÆWT!­¢êù+ªÐ„Ãj‡ N~´s°2LSè\È0—fS)žæÑ ˜©pxÒéF"õ$2±z© Ô“ÈåÁè‡èÃiŽR4ôk+hâ&4“Z*î§ x˜\ë y¨¨Ö'ÝYµ~Œù‡hJ[EÈòUUVRñM}„Jî%g9t¬ç*ÈZM3h$_¨äÜàì–“s-ÝDD>ZEûé£à¤¥ô1Ø×ƒµt3}šÍ€žŸ O‚jͤOáÉO·bµ+?5øwôáû0ÀÌþ¢iG)´iˆ¦·UÒŒÏrïäIôå@­¯Ø[p+ͬùhæro±÷S'þTòyŠ¡ä¬ƒ'~Q=@³’^ë—‡ÿáª:»Ö;Dá¶A:§ÖçYî/ö‡¼Çn£…“éDÈ[ì_òî£4§V it®÷Ó4.ä›´p€æî ùd¨tçU~>xâ9ÙnÞ=r"­¤ó àyT§z2Û*FÛµºAt.‹÷ÐÙ¥ùƒ!ãXgÑJa•Eè7PšZ¨—vRµS?%Ð~zù.À®&¾B™¼ &-L>K·Ã8!ðüÝS&ñ|†Öh-£ÏcV\·?çšýôa…‘/ÒJö}tˆî‚Ì-t>Ý .^ì¾–¾D÷€Ÿ€u)i'ÀÖ¯Ñ^J4ºW£ûX£û‰^£Ùë5:ø/MÆ»Fa>Nó4jxÆpå f y<}"<èk©~€æ3 )EeÞ‡¨¼ÍSÕz”*ŽRå=˜©Bæ¹SÕ£§<ЂDÍG©æk Z8ŠH&Ý£d_Ì£B´WCï}ÐøÜuâõ´³çâ›a‹B7(}•„9›u)˜²z: Û€ËÅô=›ËaÖK_Ƽ®,+saNl7‘<Ç)¬Ñ±éÇi.¬7Â,CÇ,OaKqTCõTWQ5H‹k½Õ¢Kn¡E@ëÒEË4¥`ek›Ë[Û|Õ­m@­(\ë¢m!ß ­|øpŽÎû±ÝÇh‚n ¢[j– _ ÌÔ#7¨õã=HB?¨WÐWð$é§!g‡zL¥Hy’p-PO_Ua}X%Qþ7ækø8=‘s|ŠáC´ QW+iiõZƒnM ´^R-Î<¶Ü ÆbßmtŽâb'ßt€ôªö­- 'ø^¬íÜ;Áÿ ~½›¦À‹áU­Ï¬sÑd(—Ò×èI(ÉrŠnÌ8<€•âæ‚œs‘‡0" W’ç’˜– Œc} Í {rÊÅ’NŸÆÿgèYçØ€ýJôÇplœw€¼‡‡¨¤­²è̓T·‰[†¨¾Íÿ5´yŠÖÁëEð9\¿¾uÎwòøäqä ¦´~ˆ6¶ ÑmòÞï!Ï¡\l)q_€è߇y¸ÿHcªÃ!£†Nè%³"r€ž§‰òûJf×£0„仕*Ì ëÞäY;« Çá9 U³‘ƒä•£õòy]åA¾Ò¥Ã"ÍQñý,ÿSlü2bëgðÅÏ‘f£îeZL¿ËE`)¨~†9/œ«Ž)ÖÌå/•+r~XA¿R~Æ“ç?4A£_ó¬×À¤@~/Ê”‡±¹DÒCÔ#ÅA¤Gý%çhœ0yŠ2ps‚&~€JD»*yI@¿J"ž¢KHVÛˆ¹AJ ’rI•é.“˜ê=Hãèê iög×f’_kî«„ãÏiåw´n|¼‡ÎO!\ê˜y ¬# ÀÌøNñvåä|5rœàä”-¦LÙD$“ç[\çñ®î,pßæõÝùɉ1¨h=OÆ×€`Þƒ/ƒ½ôNÔmSÈùw€ç ²+üPK Að¿:öo4org/gradle/wrapper/Logger.class…“koÒ`Çÿ—UncŒá@Ø&s*]7¼‹11$KHð’°`²wÏÊ“®³´¬_ÅO¡Fgâ ?€ÊxžRŽ5KÓÓçrÎÿw.éï??h G ·b¸‚Û1TP£†º4w¤¹+ͶUÁŽ‚]†èÉØ.;`XxfX†ûœ!R9¨öèÓ²û‚!Ý1,ñj<8Î>?4éd¹ckÜìqÇ{ÿ0â#†bÇvtUwxßꇇÂQ;¶® §É6m!Wéó÷\5¹¥«]×1,½)Ê@ŒF\÷s ©®Ëµw/ùÐ'.Hq«Ï°3«×:âNWœŒ…¥‰fuæâ…ç-Ce"Úè„!ÇÐÔl·U£#—;²›m"x©*­@o¦ÑÛbˆwí±£‰=C•˜ôj[Æ$‘B#‰’ î%qŸc”‰±I![3ŒÌ”‘¡JgŒ´ÇXó&Õ¡ovÂHIFií ó” Qª¤V÷(µIØ”’õ)rµŽ /‡,®Ó*üœñÈåàꊧPæ¹*qw(¤PÝ&nü_Ý–7Û›PK Aé`˜Žï8org/gradle/wrapper/PathAssembler$LocalDistribution.class•QÛJÃ@=›¤­ÑÖx¿+øP«ßAA(*(‚¾mۥݓºMÕÏRPü?JœÝ±*ˆ/3sfæÌœÙ}{y°Žù,RÏÀÁ„6“.¦]Ì0¸UÙJ.d“a°Ôà7¸Òp]Ì1¬ÿ_ 1*BÕ‚£rCT» _qá¯iX¤OLÑÏZðµŠ|-ˆ¼ Fú²dû S‡M>W|+®<Á*®>Á~0TÏвu²’h â”YèÐ&Ò㙉ôB‹bÃÝ5y]KaÝO›äµèuºF~%ÛßÉw¿-Œ;† ãVZG‘‡Õ0T\“u…‡e/ë<©ˆx<Œ6¬ðŠÁ–‚ÅO;Œ®òò5ÖD\£ß ã[øvßa‡8¾Â÷Âø>žã~ÀÖ“"nðó©0úñ4ψxVÄs!<Æ~ÂØù^ä@?fë'"^ áe6_ áÕ^ áuöCÄ›n›Òóri\5-CÍÙ–ªk¤Œ¦)ÆXI6MŰÿìì™ñ©‰å…¹‰ÙåÉ™ìÄòÜülfú¬€ÈÔeùªœ*ÉZ15G´â¨€ö1]3-Y³å’­è8?;snbl¾¾«£hÈ…’²`*Ƥ^f7Œª§TK …èªúe%o«ºq]@𔪩ÖicÛ½·Ï†øÇôEíœR5eÚ.çc^Ε¦Ë¹.ʆÊsoÑo­ª”äÀ”nS.³Ô5C®T#u^¶VÏeò4ˆWgQ±¶Kµk¶ïa÷IB¬¨EÛÙwtèsŽ|æ,XÎüÖ(š†i ( ”“MeZfC ëšm†®³´îLä4ó¬ jEÀLì³û%òlR¢m”&es•,ÒеŽ6Aiº¹½¬˜¦\TÆÕ¢bZú]'SÉÛ†j]Oe·¾&ÿ@îºÅ…Ûr1-@ ÷n‰:±–W*ž>AÓ „9KÎ_Éʯ(: ¥¬_U&Ö,E3Ùýš£–@êÒЫWÃ/M±R ³™]$`yŒR½Þ7½)x…Ê@Ä[Â7M‡æÈÀî5¾?Ÿ1mœÓm#¯ð"ú¶²áFñ¶„¯â> 'qŸˆŸJHã ñˆ„‡1-aÑ·¿5¼„Ÿá] 2rÔvRIÛj© ~Î>¿À/I™”„_ñl¿–ð¶~‹wÿò•Ï1N‰ø„ßã=Å´_vü„ˆ?Jøþ,á/ø«„¿a]@·Ã®L1Siµ˜Ñ,¥ÈÔÞg‡QüiýCÀž&e  ·±:kk–ZV¶¼ìÓíR!ªéVt•ª6ªjÛŠºu3±? ´É¸‰ª„nIø'§]c¸èç¥Mngo•ð!6¨X¼¦I5Êåp2J%Uͨ­]ÑôkŽ:ÿ¢Ý æ39n˜Û–æ¼âöŸ+~Áï—BĆvèž/íÔ\w¬i¡ïtæ”)†Ò †¿ÐgïU71KßÌäpÓÞ¿-Тû Qs9z—ÃM4'9KºLÄDS^Q UÀæßsCiëœâþŸ~äöŠŒóKÌ¡c»§¿kC qWp{š?6Ä]-hW ²E᱋iN:XðЂ±Œ»²7–iz<ñfͽ±2¿jè׸ :Z¶Ñ%€È”µ™Ù„;åßjÚ¹ÍVº/–iŽËß+åq[.™;â¹<:taœE¢{Ñ1ºùу»qî¡Y 4¿wË|ntdG¸×ÑS¤wÔOh¼ŸfKäã£gg¼!ž¸‰–xò&|ï;›O;Ûü4 ñ ZEAV¢î6<€3€c1ŒàX ÔBvç0p#ž¨Â_C OÖœŽ'jOú7ZâW­5„7жTƒD³ö*:"4ôøi¨¢«†ÛN6}#Û|÷lóu¬dcSpÝK=ž` {×ë¹õ"Hã å#ÖCØO¢Ž‘„kH:9Nºœë9ÞÀ8&(£Vºž>H"·Ð.gÉòÑÞ2ùŸ ÆÈ;C (Ž„sdÉ“þ(ž&ÍXÓÁ8ôß¾*ö×pût#—þ°K¿A7LOà.=æÐ‹»aêô*‚c1Q¦w;¦z-üÏð¼GûxçH¤çz³xÇt’Ðú6пä'Rj8Øu¤ŠèzvK‘ÃtVG>q"¢Ûò!Š×ïÏÐ]>.J}€6ó»›$¢38Oøm$Ñ×1K{“=‡y'›‘:û,8ìÉBȉ„ÓÂcõeÚÉ%׈ÜYÅÑûûÞA ±žðõU1Èù¶”ë9:¸‡e í´¿Q®Ýu´n|ÃAÉc ÉÃýÚé¶ÄW÷ ÚÉ1{I§X6ÉGsôt?'דýþKU 5Ð÷QÆ ”ŸDvQNÊ¡qZ½u½xF{±ì“Dsñ9\Z!ü—˜];<:ÏSîT"B„’o!ÿ-ë‰Èp}æ[çSóÊ)2BeäÔTjós¸ë“„/Ø#þ(Ñct`—Âû\œ:áSÈ׈8t´Óû‚Sœ ¾â”'ý)ÍãhýPK A„ÍëÂ| 0org/gradle/wrapper/SystemPropertiesHandler.classVÝSUÿ]’°!,-Ð Ò‹-á3­ŠTR°–‰ò%A(õ£.a›,„ݸٔâWý~ì“ãL}ðÍÁÇ™ú’2cÇWÿ¤Ú±þî& $¤µ9÷Üs~çÜsçÞ›ýûß?þð"¾  QÔ ÀÞP0€Oc(xSNÞ’Ú”‚i3 fh@Ô99¾-żñT,Hø;õXÄR=.âŠËr‘¸‚«~¼À{x_Ф¸&ÑÖcš‚æør|!:}mn~vŽ":»BãÔšvC §53Ž;¶a&# ã–™u4ÓYÔÒ9] ö‚aΘ€'Ô³(à·Vi=g:Ɔ¾ÏÙV¹¥K9#½ªÛ]QÛ¶ìÎÍ”nv¦-m•¾Î½†v^g-£*6`Ja©Èà#Y­" GANÅ lª¸‰”Š-|,Ðûì÷ƒg+[ò *øDŧøLÅ縥â |©â+|­â|+ÐXyvyÊ) î'ð1MnÄBʶ6‹w{/(æè¶æX,£Ö(~> WÙ¹Ê## IŽÀ±=×þ›(¾DÚÊÊWFîÙ$v tð$õ0;©À/èÓð~Ç*˜x¤BDôV©`ª -nýµ|/âº#·¿ÿŽÐ)ãÚÅøy­eðǤ2FIiÙý&Ñ^ÓÊ«,=A•5ò²ñÙ¶ì’áð¾´Tã‘mªMëfRص/}6·’-Æ·„bU ñ$eñgþ‡èRevXà|ø3%Àóò_ ๡~Šcg5hçOÈW‚ò -aŽ‚£¯÷Äï.¤›²Ö5¢T ô —cúÐOƒÅ_ð ü¶‹šå{ðLõæáý¾¾»»ðÑP;½ e™™ý3ýyÔñ؆:â•JÐ{3»P—wÑ@ô¡æÃy42ESÍ÷pä~GwÐ"03°ƒV;¸L¥M`Äôæq¬¹=çî ¿4éÈãøˆ/è£öZú‚>×u‚Þœô`iûÑNß]xÈG7Yø κcÃA=·#7?£”ÇáÇ ÆI4¹lv1ê4ýÝ$ Ĩn¼Ìq˜³R!9K€MÏ-²z›™À9üBÔ¯r‰œäWÊ)´5È"è î,µÛìÊ9z|Œ~…_=/±’ÊÈ!Ž~üLë0›Ä÷8WIþH±-_„Ú÷{ÉÿM F1µWÁU>…Ë ¯=€ÿ’‚úއ¨SpñL<`¦×Ý–_b†qW»üPK Aª=©°?-org/gradle/wrapper/WrapperConfiguration.class“mOAÇg¡ôÚãJKŸ|iᤠ" HULCß-œåH¹’»«&~*M$&¾ðø¡Œ3w ”e›˜&3³3óŸýíîõÏß_¿ Ë:¤ÀÒàaæ’`@EƒG xLá<™2OÈ<%³¨Á’ÏŽxN£8m—ÁPíˆæ–kÖöÖÖ3Ýõ5îÛ ²QO‹»M«Ž5·)÷½çÁ!ƒBwª~È+ó õÎ1í«sM¢(ê/;®¬0è/•wÄ^´°ž®9®ý®sܰ½¼Ñ ÷nïóÖ÷Z‹d,8t|åZÛkZM´lë‹ÇONlÏÚü‹¶ûÉiv,.âsñmâìî£nsúô =ÌN æN8¡u‰ Ñ½ÐÆØq$&ƒL*AîÂ=HL)£ÆìBCO*A4dF r_ ¢É s¨©ôÉ )%È  2¯)©AeEÔ,õÉ ²$!ƒÂçX Ìþywã ¬âyõ%^}9„¯°úÏ3à 2¦y~žÉOȸ¨`;¾Êä¯)Hãƒ^V0ƒYWðõ^”а¿;98ÐÝ14ØÝß724Ð3’èOt K÷×Njí-;Þž´-#;¾[ÂüN3›·µ¬}HËt eû’]„rbbßàAqy1ypßÈ–mÛG’C½–éNŒ$ûº*=” 8%g™9ݲ =/a©£zÁ62í‰â9©¿ ÄuÀÈþ NÃlç=qÔ¤Ìì˜1.aCi·[Z:£·Ÿ²´Ýk?ìÌ‚§`i¶aféRݘi‘œãzÊÞoX4šÖ”„­ÑrðÛ!vÖSºF`N3Mš-ì1²z_arT·µQÖ5Üc¦´Ì!Í2xï²Ý®\ò)éá&* Ÿ´µÔ‰^-çÞ«Ùcd {¯„õZú»nÃ! ]«÷$E×锞s=°' r|Ë™¸4gé9ÍÒ÷yJ¬Ñƒ Y†„EÑ Ž€¬n· ts@òfÁJ•BU¢(Eò2æÈDK×ÒÒ²2t!cjé„'-BF–Y×&‹vÑÝÙ\ÁvŽwËø†Œ—d¼Lî×m¯z¤”^B<úÿd‰¬ )G£Go..?¤n.¶LÆ—Öaš6é§åz5¶K³lÝq hÖ8Y[ç#ƒ”0H M·(aɨþ-ÊÂW ÃvМ$›ç‘ûÜ`Pm¬‹Þ¬Ü?}U·b§ú4FÙásïN‘Òú˜VÈÜè\;ïéˆ/TÈÒ,P¡§©(ŽHždHW)¯(±MËî5òyb-Ù«$E;µ¸¤¢"6² Íÿ»vÈ o±JˆVð;Û6w»±ÔîT â›$Ú¯º™ö-†T|ß¡Æ]²y µI½XjZ]"%ðÈ)Y¿6¿>’6i›5íˆ~šªg#•M «”;•Šïâ{,í5Ÿ;¬¯ÃëUÀkUD· u=‚£*®ãû$$í)Õ-¯«ø^Q‘ÇI?dIe ÍžPñ#æø1ë½Ää„F/Q²0©â'Æi?e6õ!#—¤(莄Ÿ1õçeøLø%ê|º%y°Ó,dÒÂKÜ¢"§|j™“¡ÙS¿RqŒmý5ÿ¿Uñ;^ýG©Q¥Ëšh†&Ÿ…}#YÜŽI¯§7w<}EüAÅñ'æLÇQQñWüMÆßUüÿ”°¯ÏŒˆDœ2ì‰È }ʉx>§§Œ1COGŒ¬¯)7rƒ’a…W¡äTÖÖN{üRçÓ“Ë|88a™§œ÷«ÄÛïAXTY¬Öø¾oeÝ‚zfu”Ç‘´y±¥º®¡çtR³ý{…·»:‰íßvêý:ÉZ˜¯|`GË_8檫x†8󨊣¾¢êò~Ì•œ£7ó:§Ë+x‹µ@¤ü-I Ý:bäqJÞ»q(¸Kq6±Û·ßb¤…j &™šÐù˜Ïï};dm§?JßpõEö¤“›)º@a Ú¦¨ƒÅ•führ%’•Qßæ¦2&ÛÔxëg“lL¿->lDÚÜé×ÁM6øuF¿g–ôÊÛºߊ C±šþ!Ì£BµhÀ^ÜG»×Ю ›h¿Ï³_FûÏ~í;=ûU´ßïÙ7Ӿ˳_‹0·kZ‡¹c‹™z8ÍóIvz‰³v¨¡Ð0ixUñpõ,sÏ¢fò´€ë§Ña\‡ªs oе(ŽC´ÖØ BÏbùj‡ÃJà%Ì®Ž%¯@Åü«,,V.£ZÈX@3#Œ8šÐæ‘Õêš‘¼RMô`:B¥ûéB€N¦c³X@À xÑe,޵^F8¿Œº8isOÆ[¯`IìQfQ?ƒ¥b^ÆÓ –Ï¢aÎÙ ç¬Iœ­tÎV9gw‘‡"3Xí6;‡kc‹s¶Ö9['ÎÖ_@cŸpÃÇ qvCÛ,bW/’þ<…guÐE4n"kÖÐÿÅÍXBÿ™èeÛ(¦Û‰rÅ}îÇNLbÅnº¿—öà<îÅ ÿ„pa€öKq‡…K§éÆ0 VìHI¬8Cª„K—¢ê?RMÁ“Q%Ó¿Vš£tʯ<Z‹0«hn!çÅ{È£­×9o(g.¡nmt²‘rgíÓñRHÃ"2„¸ü ÑLòŽDÆrõ â.<€7’^¬MÒ«h–Éî7Ñ.@§#Ä}¬¨Éâ "áð¦l¾%Þ4‹-‡çp÷°XnŶÊÌê%úPG ”2kµ«‚›Yu™ è¸2Ÿ&"[²rÛ‡É÷ôÆ[g°ƒ~;/@éã¹M´–dÕ客ÔäT­¥P, Ia’UOs#ÍMä\Ö#"t[éf8¯80Œ´£HyCTÏ!âøÈHsˆôš¢ºcô£/··ÐÌÖ…œTÜUYÇÇ<¹0ýßîÞòýÊËãžËÁâåã8áú*C9ö­ŽôÝ»mñÊŒ=‹HN@N3XL¹Ìˆ[[Å\ mRH # SdL9¾\ö¸ÙŠÅ¥l¥ 9O†ŠC(ˆ.ÊH']¤„Tí µú!Ùž ތīS´ª˜§]ÌçhµDL…³yW !p 5 éÖk¶N¯<ÚXœzZ*%q3a0LQwxˆ|q†,8‹åx˜’é¢?æñcKQ—–¢.-®.¼š"öc#ÞLg‘d ª_EXÆ™`-q-z3AiÌ·šD#»7è6²á×þ¶^á—Ç=n*êÒäê"á-‚ÿáÿPK AÔe‹ #gradle-wrapper-classpath.properties+(ÊÏJM.)¶M/JLÉIÕMÎÉä**Í+ÉÌMµåPK A)gradle-wrapper-parameter-names.propertiesPK Aorg/gradle/cli/PK AÕÜ?®<S1org/gradle/cli/AbstractCommandLineConverter.class•T]oA= ‹ëÚ"¶µ~C?”R*O¦Òh4!­ÒÄÇaY×m`— C£Âߢ/4ÑÄà2ÞP–´ôaçÞ9{ï¹sæÞÝ?þPFÙ@;:v D°c@ÇóÀ{xY9y†xÕõ\Ycˆfs§ Ú‘ß±V®g{m[´x»KHªá[¼{Ê…ì' &?»†bÃNɼӵKV×-ÕÛ)¸%ü^{€ìÈ÷Îm!mQaظÌõµÿŸ¯|¢j«U©›n†Ãl㌟óR—{Né…¬•ì¤}f[²’ C¤… ‡´¬-¢ k’ôÚ–Á=,È÷¹Ø‚ak^ÀÌÁß«Š^tÊê Ö”Âõ¥-JŒ×_,»/]ßèØc¸ÕtË¡ +;Xš¨rŠîCµuVQ CWˆ uÁhúCaÙoÜ ™«ú¶Ô1aà6Cúºë2Qľ‰LÜÃK†Â &„!9¯‰a{ Qá23Quá {¶'§M¡†Ð~rÕÝìõ3|i153 ¯mnžDevf¨èÎ߆J…£–š~-C…Hý;"d©9´š´«“edcù °äDq‡Ö¸SX¡Õ`I²w/!)²Œ:·FyÝ7²ÙÍ_ˆ|¼@ô8_AË #ÄöFˆŸVX! ÊK`öªR~œ;©xëô†)ï>=ŠNâRnpŽ´âØœœcÿhÿ˜âŸ¯òÏ”ô4=c/ƒ-e·ÿPK A׃µ³Xì ;org/gradle/cli/AbstractPropertiesCommandLineConverter.class­V[WUþN2ÉÀ0å H¡°Ô„¤ÄKKµIQJ©†‹¦‚Ø;L†0fâÌáOøîZ¾ûZ_¢t­ºúìÿñA—·}N.iÊ2Y9çì}öÞç;ûvòëßOð6¾Q0Œ÷e| `ó|¸£`w,â'?ä«û ÒøHÁÇÈô` Ë=˜Å V»ð‰>íB¶ƒxÀ‰Ïø°&cë.cCÆ Á”i™Þƒ?]cì¼ÁЗ1-c¹¼·i8´Í"qB[׊kšcrºÆ”¼mÓe¸™±B¢àhù¢‘Ћfb~ÓõM÷V»d8ži¸ öÞžfå¹ÙÛÚ'¦á$ F]èp¥ä™¶Å0‰fv´}-QÔ¬B"ë9¦U ёѻ†§™E#Ï0ÖfÏÕ³f±[·­-³PvòTä4ØÐV5Ç%\ÜÁ’X3L¾Xž¤íÚQgIW‘‘´¬W]Àp§0™?¡•¬:£ì™ÅÄ’VJFOѹ’MA`i Ò®q¸¦ËÆâAÉ1\W@ µó¦\ì¶AÜŠ€A)5"É“ã4‚¡æä8,ÕäF³hªJ+gŽ ^Èzš¾KÂŽŒ‡añ@7ª e<¢ fÍ‚¥y"¨ß¾´;D='þûš+›;†î%£­,†üW»’J /G¬dí²£÷L¶Xg<Ãm©x# ý§íªx ã*&0.ãKo 'ã+A“|gºŒ<ß¡o©(`[†©b»*ŠØSaÁ¦¼¾­¢„¯ Â«ÂC™û”%M—UÅ4¥Ü¹zÝù%ÜLEu|tššg·1qBuÞ)”÷ Ëk$1Ãø‹ú 5ÖÈÃÖxE;i1g¯÷Yu[së°¨ª¯D:2ÜKZMÍu6ò?Al©†T'¶[ô'\íL’z ½¢o’B‘“EŸ1]^„½Í†.³õ¡&…z6ðK®Z6<ñ¶ÒS+Y‚h~ßu.›VÞ8XÙb·»uš÷ù2©¿iÕî°•t»ežÔd£N§Û¾³áH;¾4A†Á?2/y/• ™jéŸÀ~¤…£43Œ1𺸄ˀ$ñ¾ æ‰Ú<Ég’yWjFK$í§ùj,ð ¾ ÿtþì†D“”­ °¯ ¸'R® kýøÔ$G è}/aŠNäƫ֪ÄjŠ~Œäú¨ýDH›ñæARüôßèô ͹?·‚î ”#ô0,_;‚Êðiqá9|·¤)Ô[Aßl øq"û0àÇú%ÊT‘Âéq¯Ô¶þù9þ„NêËäR7¦È3ä…wÈó„oƒ="\ü× Ó bôÓjŠèkDóÛæ·ÊQ0Þ¾Îá-úé#KuïsÎuâÜQ‘þÄ€ŒÙ¿ác¿cøÚº)Q‚á=áÐ[ýótx’V¿p¢2‹?Ç`á'g[©:¶é«YMᶘçþPK A}­ÎyGK1org/gradle/cli/CommandLineArgumentException.class•‘ÍJ1…Oú3£µ¶Zm+êÂî´UÜV " Âà–îÓi˜FfĘ́¯åªàÂð¡Ä$-U´fq“{rïwOÈûÇë€34K(`Ë„mu çœ žöê‡þ=}¤^DEèõSÅEØ=.å˜T}.Øm˜ÐQ¤•š/ ©â&Ÿ‹…t‚_ªÐ GÌ "î]Ê8¦bl *Ìb&Ò«ç€=¤\Š.³$¡¡¥þrAÐ^âí›2˜(ùdæ[ÃÅ€f‰&Õ—V”ú2S»æÆoë/c§PFŽ ‚Î?^E°û5ÿ.)Ùâ-äõ/˜•1ttuÖÓyNïN»3y±÷+:–¬ÚÔ•;XէƬJëk–â ŒuÍ0¬Êœu£gäõî¶;ÇSä~ÂötÓ¾…ÌÊ0w3§*6¬ÅMÛ]ûPK A³ßâúg)org/gradle/cli/CommandLineConverter.classQMKÃ@}ÓÖÆÔ¯ª'Ï"4 F<6¥ EQ($xߦë²%ÝÈvSúÛ<øüQâ6…L ]XvæÍ¾÷†™Ÿß¯o÷8wÐupJp’L-¸6„~/œ²óS¦„ÿb¸fã”%ìu<å‰ ¼*Dè<.þid¦æÎn$…b&לpW'<(a‘ÑR‰`ÄqàÙKxî…™¾Ðl’r?I¥ÿÆôœOFÙlÆÔ$”jçήwÚøºvRm_Uˆ%J¡¡ïÐÄýªñ°vJQ–ë„?ÉÔê_”äFë%p}»b.·;o¾ºÿ-7Û‰Zä3®ÌfSm¡Õi¶M´l¶g³–ÅÛplÔÀ~Qqk*[9Àa¯‘#ïÉPK ASf Õg&org/gradle/cli/CommandLineOption.classV[wUÝ“¦4™^ˆ´i¡\ä"iBµ´ ­ÜZ.Å^ÐÖŠÓdL¦™0™pïwü¼ø¦¼ð .UYË哾ùü.—¸Ïd:™Ü¤Ë‡œós¾³¿ÛþNæ÷~þÀËø&Œm8'ã|œkE–Zqo é¢Þ‰à.Eð .‹A•±AšŒwÃh/ÝÊ iEHzWœ¯Š…ª8ɉÁC^ܾ&KFA†-A6ó¶næ :¦®¨×ÕTÑÖÔœfJhÓ³9Õ.Zš„Ý•§c¥¥¡æ²©9ÛÒsÙÑü¡¨V¶¸ªåìù[y^ÚäÓ7ÔB*›«÷Æâf$£Ò–î8#!Z /!¬çÒÅeÕæJ‚´Dÿ²–YÌ/êöŠ„¡jÿL+›ÊZjÆÐRiCO›««j.3¥ç´YLjão8£å--­ÚZFB˘žÓíÃbqŸùI[³ÔeCí_73šHafŠ«Ëš5/΄ÃfZ5TKkw³Åtà Ú+:3¼ëÙNUæÇ³-¡»ÒÓë©çkƒê´ÍÙjúê´šw®Ê(ʸ.!¹a‘pV³g×)³)Þ_Cš½Õ{ œ‰¬¨…c.[$ Çk¨Ò¿‘lª¹'è´¡«{âRS|~2ävÆ?á§êæõx+ÉÚbi…¢ÁÈzkNu#£Y¢ j>¯å2ÓZ¡ fYËT¼¡òR­QŽ–ûxu¤Wx8R‹º± Eë1Ã0oøÐïgëmñަ¥ž74ŸŠ¢Ór_q9éë\…WO•w¸–/jÝh¼Šz"#ñÿåÐzÎ,Zií¤.Zª»FkP *Ø$}ÙÂiµ°B+ ¶c‡Œ› na·‚]bèÇïá¶‚÷1£à|(¡³º 2>Rð1>aÑÊ Sz´‰5 ‚‚½xAÁ$>UDœé¼P~Ã.*8…Ó ö•Êï%>Ä‚Ïñ…¸û¥‚¯ðµ‚;ÂôÎg¥¨ÂñÙå+ZšþEkߌõM' gÓ6-‡0 ë:ú ¦NVçÏŸ=!!¤{ŠÝLX`©e2zF»I«Áœ3Uö\É%ÑNµ§e5CC]ñZ]AáºmѸYC¶YÚb×Z.+ÈËØ&%ôÅÇÿëb Ý`¯ð)ð³sÜ4 º"((¼á{±j^×ð<ÿ¸·ñë QÁ*°>\¸ÄòÖ{ø#™åæœóÎü%ýNîíÇ׃\ý&9ŸI<†”x‚ÀùÇhzˆ`âG4?DKyGN®!$azÿZ%ÜÅV a ¿"2“ø Á5(,Þ{ú碑⸠!ŽÛ¹Š¡=èÆVú½£ôé$¥Ij¼Hôa¼Dÿ›„/üi;ÐßaÊ çÒÎ¥ƒ”»xÊšdôÉ8$ó‹¬ð«TŘfŠ+q½Yøúƒ£r²*6÷:^”ð›]Û%׸wG\788ˉ¤H€ir`ÂÎv?/'¨î’š†ì–ª†ØÙãç¨cæGaf„;B'”ˆFÖí|vévÊçrȵ#áx=Œ¶zCÄ8Pc.ÆÎÑ}‚vr cйëü JR̰i1!¤hô¹ÇØìˆ]ÑnŠÉGˆ•S³…†À¯Ö«ÓÁ¢ô0§»ì ƒ.§jÈ5"GO¥ú† ý™ªñ޹nÝæJ0¶·ï.b4Öó-ÂÉÎл˜n=ðŒw³"`´á‚~´r<ña³s>Ìã)C—ÓÕ0Œ9y®mjú®V!:«©|¸Šk‚Ø"%²£ƒ…*ðÕý5&˜@TZ¥[AkI )†áª±Zb²=Níʆr2¦jŠÁ³ å¾sš¶噜É(…B`4JY:TCaôÙeºcªùU•¨°*çœb2ô”vŒTzCɘ‡gKEËÔ·N„ö¶R¯9‡r,m:µïBM}¯V´Î÷{"O.6¸ÿu1á³ ¢‹~è-ôëg~¿hW´ªƒKt/ÐO‘vs´¯£Ù }„+ ï£î=í]h§±YèØ,êÙ¼l$ë Ùã®ÖŠúÉ®“7õ½ Ü4O?Àõ îðgԻ𠞥þïh;_‘¦!ÈöѸwiýÞõP˜¶¾þCEÓžEVÐh`l­l í,…[±¨m'GT&p ÝDA¬nÓÊE‡ÀÂé!iØ/8<~Ù[$›"­ÀáVôì8z€b3%®ø‘+Ž>gÖ*hÅ/V!r'î·ßÂÀeš§Ém3=³:Irƒæ›n w„¿NÄ0ñ¾Š!ñØŠuñ2‹aÚíÂÍI·ôPK A¥D£¢&3org/gradle/cli/CommandLineParser$AfterOptions.class­•mOAÇÿ{-½r-´¨ >W+p€Š"ÆDð!$MFxa²´g9½î™í¡øü.¾D%ÑÄà‡2Î^ÏrÒ’cšìÎÌÎþffwnûó×·fqÛ€ŽQiŒu“4®£h ‰IS°tLë˜aÈT¼z‹jÉ6ÃPÉ“5«&yÕµ­ŠëXO¹lØÕ¥}Ÿ†ÔG8þ]†‘±x÷ñ2CrÉ«<§ +ÛõM[>ã›.YN”¼ wË\:JIËi0dï½ômùäïxB©ËBØrÉå†Mªu0r$f„Žî§¬óuþ~Ó^õ¹ô›F†þ±Ò+þ–[.5k՗ލ-Œo0$¸¬©äÚz<Aè˜eèkÊͨ´èS ÈbÅÑÆVçï…ùg<±â‰?ÕdþÊ`±Smññ¢‘t\cX>ÂÇcg‚>Ðv¦Õ@]7|”- ƪ·-+öCGõÅ@›Ï”ª0‹ ²j¸žE7 7æcékâµðÞ‰W7xØ’Š1§ãf·0Ï0y¬d˜8ÎÙ3Ì¿5èó?U†Ñ¶=ì¾ q~ :5úh˜hþkºmqéóåÕêƒ_ò2w·íC^€2 ô\¦é ¥G@õI]ÐTŸ‘¥‡´G¤i4gL¶Í,~Ab—T ½4ö"A;Ÿ£‹­Ã`È‘m éŽò:CóÙ¤ú÷3q‘æ‚äÓôhbÆoPK AŒ‹Müu <org/gradle/cli/CommandLineParser$BeforeFirstSubCommand.classÅVërÛTþޭ䏲Ò:¦I à§I|‰sk›ØiK’^‰Üœr“Õ¨±¥Ž,Ó¾À[”˜2ÐþƒáxöHnb×Nä0áϹìÙ³ûí§=«ýóŸ_~0‡‡2Ncñ K2†°È‘“!aYF—ø‚«ŠÍ ǪP»&CÆuŽ!Üä¸%ã6>’1€u޼Œ(68 wúoŒz|†a,oÙ•LÅV·«zF«™5«VSÍí¼aêwU»®Û9Ò^6LùÂpsÒ_ýu W¼Ý¢—›ÚdÖ¬má”l4jeݾ§–«$‰æ-M­nª¶!öM¡$à2 ­ê-[¿aØu§Ø(7m2(·MS·×ªj½®“Ú¢/ÈxWCiX;PíBNg, –YtTÛ)§âò}›ø”ã3%l1ÄüøTðŸ3,ùzºoî˜Ö³Kfž=ìHÁ9Œ T_1$w°bW5Ýt®?ÕôfœkšŒ5s$]%½˜—€±‰ñúÄ4Cä Y åGºæp|­@EYxÔ.ù†´~H@ÃÝ„]*ÿ[qèçrå‰jëí€;˜;þk§G柮G&ާİÞSEêõQɪ¦éõz|q†×|ïyÊóÕpŒj†Š( mªjÝa8ß ÓË”Ww[E–‚ñ^lqŸá6v¿ëÆÊ(=­»WN#¯#¡rN©YS‰–¥.8ÍÔ~éêrWøûß´ö¿€¹ÿ¿¨KÇ*bÔ; QËÅ"Q¼iD@Ôr0¼M»[´Ò¬$’?!H¦vüö¼CãIqÆæÑÇfð.ɆéŒô1J¶á®¨–’ŒáœoZý }4«{JÉÛEßFâGž£?õ3x/ÊJ#Òï˜õÄ'¾Gtr)IÛp‚ô•g{(EOJ¿âT)˜.>Gdƒ/ö-¥G¤WZo=sPß#—`K3‹Q–Ã[FŠ]FŽ]¥¶pÕ…¾àÚ‡®b“Y¬¦h@˜~R ¤Èæ(®!ijJ3n¿‘ã8B§# g›¡HW¢™»Ü±îú…a6ç:Nx*ûŽ9µÀg\Çó.{bµ€ î×¹èÚ¸„÷i^Eˆ¾`– Nó¸$úä,’ÔÏS‘DŸ¼Ñgq¢ŸÎâc)€/hþR’ÿPK A´*«ZMïForg/gradle/cli/CommandLineParser$CaseInsensitiveStringComparator.class¥S]OA=wû±P·RˇŠ"ˆUhAx1¦„¨MLšÔ¤„÷¡êàv—Ìn‰ÅÿÀ‹/˜ø`|öñãÎîƒEkâCçÎÜ9÷Ü{Ng?ÿðÀ&6 °Q)`Ì,9Ü5Ë=K&½l£fcÅÆ*!¿¥|m2ËÕ]B¶t%a¢¥|ù|Ðß“zGìyœ)·‚Žðv…Væœ&³Ñ+æ"”M?”~¨"u$Û‘V~¯ô…Q  NÓ÷¥nx" %œ©6mÜÁ#¥nÔóÖ›uBå_Єñ¶êù"ñ[šf)ÏýåØÖ°àm¦*´ƒîȧÊH›j·fŠpÉÁ\ëp ¥ß©LbÊûúèÿÙsì‰ ÂÔEŠ‹£Ý"L¦ïd'höü@KÓŸ0}Á›1/dü MXEÏnØü…qsYXÆ'>9|r9Ç\í=¬w¼±Pä5o’ô—yï$L Ì‘Œ…i±ft–cyeõ™í¹·ÈÍ›}ö˜³™˜©lôy:E¾Á¡1k-©LYÍn3q§2®òÎâ\Ãuæ™M&:åâÒcî#íÿ€ï2‹µ•OÈ­òïù㿈(&íJ`æR’‡­ÔºØ™p6k·0Ï÷,ÄøÛXŒã\‰ç·Øî*w™ÅM/ýPK A|ìRÎÐ&=org/gradle/cli/CommandLineParser$KnownOptionParserState.classÍXéwUÿ½6ͤÓK‘*EqDè’t‚;VkE¬´E­+. ɤ3ufÒ‚ŠHQpCqPð![ÿ>žäu¿\|ÂÿŸIø\@-V< á°À¥gØZ§YÍ“ˆæ€5ͨ-4Τ¦j§×ª¨ 窬ÓÕm͵løÅ Z n­ÍïÔpi S¬5ê¿—˜$뻦 —b^¡)ÄÌSº™tçÐd%f³ùâsT‹CPÅž`Åg^íá •€ôt ¤µÒÎH‘@Ú'ÙØ„N‚rŒ"#ÁŸ^häÛÁN°oGQ7AUß‘çt$‹ åYFRkº|²ôžÃyÞaCè@Æçg‚²°pPÆÉëèn'»Iì<:\(ð=õÄšÆpÑÁÿòÙ,§œÊá›Á«ía…µîà¿Ò±<çÔV²â8æŠoßSTÀ=âGŒˆŸ°KüŒQñ ½’~Ãqñ;~àWqÂóW”|²‹ò¶›â\Šãä¹ÊTÎÏÛ°kqS Žä'×Í'Q.á–¿QsŠw8¿Th·áÚDL)Ù GcMóCG!O Ø+žm>V6ådÜŒ¾ hd¬ ŠFÆí^Ñð©wŒ¬÷øÞ‰&Ï aÄà ‘îWÐ~qˆÿªpÐN߽̾2BIî þ7ÄÁ³!ù?PK A$ľ¢¥ô<org/gradle/cli/CommandLineParser$MissingOptionArgState.class•mOÓPÇÿwÝÖ*ˆLDÙ² ‘!¨ ·'š`0ÁWÖÌšîo;¢ñoôÓøB…Ä~?”ñܶÌKË’îœÛs~ç±íŸ¿¿~˜Å’Žr:’È'I*(uZG 7ÕÙŒ’Šq”ã˜eМ-ÏrD·†_øòK.]S®zÜ3Œ§B˜²js×5]†¹š#ņäuÛ,nÚV±ê4›\Ôk–0¿É#%Šr×–wŸ¡’í[cˆU:å3¤ WZÍ S¾â6œ®9›Ü^ãÒRzxóÞZ”îÈsËu-Ñ˲VµÐ;‹®žTJºÉ?n˜¤I/¸GQ²µw|›m.ÅUO’ÛRn!ÊeCåwä&à# Ö» º@ûë~Ê+ŽØ•:0ßÝrïæ`€´#:L‰:՞ͭÅ1Ç𦯉÷ö)û[ùPR—2Ãäq\ôU§%7ÍÇ–Ú’Ñ#63ª†V—Ûtœ201,¸ˆKî`<ŽE†ùþ–ˆaú$ýe˜=y&z·ƒaêà °ÜýÒýioÙ¦ SÊžx[t¿­¦)<†L/gdè핤÷=Ij$iˆ¨ ÑÉ0iOH‹Ð*ÏöÉ~"ºCj„ÌAæQòÜÂ{Iœ¡³ÑÀgI‚/),£ß9Œ…ÐÅªå »ˆ}kó4º¶ÝÁÑÚ çqÁçЖ„œgŸ2À`þ¢»(LïBû~÷ÉÇeÃ6n0Ä)I')BòeŒ‡à{a‚I¦㇡Ÿ;rL¶¡ÉvŽ\ Q_Q”ãëÿíÓ•;û‚ûêãŒÀ0À¥AS¸B’e4hØØÎ¡dšv6lÒo¼’®ã†_єš Äh8%1õ+aêûUÂÒ' øPK ATK>ªÄ=org/gradle/cli/CommandLineParser$OptionAwareParserState.class­UÛnÓ@=›¤qâ¸$”6\Ê%¤)MÝKÚ@)½p)E¡ E*oÇÓĮօOá xá$ ˆJ|…˜MÝ’*A./Þ™³çŒgf퟿¾ÿPĽ8È©Ð0*w×TzŒ©Èc\…Ž “*L+(¨ˆcVAQÁu†„á4›Ü®•,ÛdÈ–Q/Ô¯5̂Ѱ O¹pÍÚêÌCÔ{i¹¹™è\û èe˶¼; óÁð`ùñ CdÕ©Q²IéØh5«¦xÆ« ò ”ƒ7*\XÒö™.Cúɶg9öʾZÙãŵuÛ6Åjƒ»®I¸…À,s½™è]SMþ¶j’%¼} ÃP¾ôŠ¿æ…·ë…²',»¾4þœ!ÌE]&ܤ–8ö†c$Ž${¿]pÆGÓì§±õ˜oûRËNKæš%t×éi©¨!‰”†Ó˜ÓÐSnbXÁ¼†[XP°¨a Ë s™¬¼ðL±f ×+·ª~˜a°—[ªÝf˜:ç~½¨Z§)9höæOØS†É©­‚» cÁsž›mÏqÌrºœ |ê¯Õ¼ñ¯ðFËüËdUº©ºÔ©áÜ0L×ÍÍÍÐ-=ƽ”»õ_îïqk³Þ¥vr®l0.’¡Ï¥FÒå|©”œxò„¡!òJ„ÓšÒ'vÁtö!}ráOþYÐYг,T6‚$Ëaˆ|iŠÑZÏíÔç ûÌ‹dKTTŸø‚ȇC¾(ÅÁò<ÑCž(.âÅ.#ãóì ¯*êŸ&ª>ZÙ7Dß!½eSZ2B™ÇÞ#¹‡ø¦oªÛo+5UÉÀ¦cÓºÅCÝ¢¯{•ö B£*^ÙvÒ#8G«Ni$èy6¢˜Ž+ùÿÑ1Eˆ8­3$r£]‹ØoPK A˜%àÌ»ª7org/gradle/cli/CommandLineParser$OptionComparator.class•TmOÓP~îÖÑQ:¯ ¾€ Ý ƒ2@Š.Ñ,YÀd†Äe4³¤kIÛ†?„/~#‰á³ÿƨ_ç¶ N6).]ÏË}Îsî=çô~ýõù €E”%d0#¡Ÿ¿ú0+bN‚UÂJ"E,IHs;'"V8|UÄSk"Öz6LÛô7’eA(;íªi;­æ¾á¾Ñ÷-ò Vºníé®ÉíÈ)øïL!»{䛎]všGº«ûŽË WlÛpË–îy!–ªŽÛP®~`jÝ2UÂ6uû€§y­»žáN_åÐÄz`Q¢W…kÂH-¡TˆÓ ôÃT|À%|‘ÎÚ4yÔ`õP?ÖUK·jÍwM»¡…kÉÚw÷º¯uz”Š-ÇÖ¤¤ñž$Þ/0LßÍÐ[3¶î·xѪÿÚMË7-õO¥7â ±IÔRÍi¹uã¥É;?Ú‘~žSËÈb@Æž‰ ™Z½a×ÃB¶ÏÏh÷Οñ\ƶið®ö‚a+6eY÷ŒŠí¶gúæ±Ñ™{"!c /&ãªÆqã ¥ÿý¿vŽa¸[ëhŠãÇ‚Z×0ü0 }‹¥m j†¯]{–…a¤=¿eÑžBº$Í>•½Pí¶Þ}à4¥s2¯|=ѧÕéQ*t±e躣KŠ.@ >rd ’¥’d$S3gH|$%!z÷p'û†aÒå€Ü!Éx‹¢àD–"¹2{Šä9„·ì ©Oè¹€¸Sìp­ çHs»wnL8…tr™­I"þŽ!öyö3Ⱥ2GY¹v÷‚¬à>i<2‡˜ Ø<l’4!»M»{ín•Vˆ™™Ù ôé ùäš#fÂdYþä"’5’‰¨>¬{}FCÀåNS˜Æ#ZOâq€Ï£H·INQÍ2(RÔ8éô˦ÉSÄ<Ùi’Ëÿ PK AäfC˜§£8org/gradle/cli/CommandLineParser$OptionParserState.class•’ßJAÆ¿³Y³º¦ÿ4Õ¶Ö¨£×JoJ¤P¥… …H.¼›$Ã:²™•Ù‰ø }¯/ú}¨Ò3›€ˆ…4»ß™³ßùΜýýçá€#ÔB°ÂÇF€ÍUBñXie? õ½Áo¦}IXl)-O‡ƒ®4碛pf¹•öDÒF¹ó8éÛK•–ή­Jõa2iÚVXþTú®µ4ÍDd™dËÇVjâ(6¢ŸÈ¨—¨¨™B÷]ŸQ]í¤AXH5‡ÆžÊ[ûÅĶóOúÖ÷&£ŸBÃT3k8ÚNê­+q#¢Dè8j[£tܘš¸KûMdT¾Ì‹¼W^'ÒÊÛ„Ýúdð‡†[ƒw{H¨ý›»´Ó¡éɯÊm¤òÌsàÆ+aÅ;„£é÷@(?^ÑY÷JöxÂ÷Ó\a{ò(„ê$“_åÿׇ{ŠðÜL |ŠX‰ufÿÞfs'é'æ8. ñðygXÊu+¹®âe®—/;çÚþ‰ÕÃéßðÊÈ0‚çÑ:^çeorÿ[nÎø}‡y?Ä"k™õ•³—gÿPK A¢Æ÷E«¶3org/gradle/cli/CommandLineParser$OptionString.class•TÿNAþözåÊq”ZDQ‹´GËQÄŸ kLŒ&(ÿ[®—rx½kî£âø/$‰&>€ï¤qv[l“B“ÎîÌÎ|óÍÌîýúóý'€9<éE?LÄ´Ž4ŠB-¥0£ã, ³:4…( 1—Âmá;¯á®†{ Ö²ËÛü#·<î׬µ8týÚCOЈÝÀ§Í¢ë»ñÃT¾Ý¯ÝRXgP+AÕaXv}ge§¾é„oø¦çˆLͽuºBoÕxËŒU™° Cê ßwŠǣȡSk9kV-äUϱlϵ*A½ÎýªHñš‡‘æNÆSéš?s£†Ç?¯ð:幘/t*´-æö‡W¼!éh¸ÏŠƒæ±† /ÏTww~eÙåÓ¬e!ærg‰cèã¶íDQ®ä¹ŽÀOE„–;McBÒ»…ÛÉ-Ü!¹%Œá.ÕÌ"‡{ä©äN'Möë¬zz‹ÆÇY%¼B65[ümŽ~çèkÿE“T‡BZÆ“q‘5²<”]/h¦¸äŸÀ}LѾ‚>‡‡‘ÆM²c”ïGÃäMHxº¦) ˆ±ïPK A`M~U¸2org/gradle/cli/CommandLineParser$ParserState.class•SßoÒPþN) s€ÛÔ97Õ½Œ&MLÈf‚ÙÃÞ.а.åÖÜvFÿ'_|ÑÄÿÿ(ã¹-›Ý Aš´çG¿óïœÛþþóó€´mdQ³‘C-Ç6êxbaÇBƒ{áI/zIÈ4šÇ³Œ\ÂjÏ“îáùdàªbàs¦Ò †Â?ÊÓñ4iF§^H(¼*tU?'‹ï¤tU×aèòËv/Pcg¬ÄÈw¡ï9Ý`2r¤;$uõTy‡Pšˆ/—#}Œ¼@Ö½3ñI8¾c§)OŽ;ÍBÞ / ¡ÆZå Ž°È›…&¡œøWtŸÌi2§íâqf¸YC!‡¼ûzÞPÚ•h&ß—ûP2£»e²»gÆv¡nÓ‚$Ù$T@|„*>AŸÑ%¾ W|Å):ËT Ð3€¿â‚Â_qIÅ_q3!ŠžÅ¹ t–¯^= xÎc @椚ÙCäÍ!ð÷:°Z_ eªàYòÊqà>8YM¨ÝHÅEÿF‚Ÿw@³@9\"žÙ!µvÚC”œzÀ¨1‹ø‰vñ«®¥x••&Òƒá€k$}ËŒfÇö!¶uõÁÇÏTsj]Åp9Ð9†+Î1Œø:ó*…4ñ*4fEù!Š´“æ¿Ëœ¦./1YœŸÍ ®‡µ?PK A£=l)&org/gradle/cli/CommandLineParser.classYi`\Å‘þJš™7zz¶eÙ²=`láë–1¶°åS>K6–±‘ÌXz’Fψ™¶8B¸ÂŽŽ``‡‚¹d ÙM¸ÂÈîØÍ²aÙƒ=€ !lÇÇ~ýÞ›ÑÌè‰üPwOwUuUuÕ×ÕO¯{ö€YòT:ñ±jþW5¿×±Ÿhøƒ/>Ññ)þ¨sú3 ÿ§áOùø >סã yøRÃa#ñç|ÁÑ|ìÇ1ÕׄW‹ø%GÇÉÕÄ£c¼xýâS¿5ÕøEžŽ]¢û%_ͪ¡fFúeu‘¿ŒÖ¤PÇt|ÂeŒ&cu”࿱—qª¯8&¨& œ¤äž¬š‰JO‹ï”|7#ñP"t‰™M$(°¹ÓùÎL8?²=ݱ×cq3Ö”&LMÖsÇsO°¶A0ÚeúAQC(çÖöj]¬ÝZ¢-um 3fÏÆ5iŒµfÎ Å≦îmÔkG0ÒJKͶhÌÌš×dcÊu;ƒ13cßM‚üŒ‰ÍÜ0Ý[‚1«×¬«_ݸµ±®aÅÖ5uëÖ­XÛ(¬ê ^¬îN„ÂÕ1³ÝÜU½&˜ V‘ù‚QQ[Ù¥=I£Ò¨‚]¤Ék µG‚‰î-\‘¹ºÀþFÚ«móWEcíÕí±`kجn ‡ªÛV…"¦­ìüE”9:Gw6„v™­Ž»Êú1Ö|ÆyqeÌ41ë×òP<¸-lÒ‰¾¡H(±H[RÊsô,‹¶šJîÔØ½c›[§…«¢-Áðú`,¤~;“žDGˆ’§|…º¶³©¬·Kg–lloi¶‹­5M%ä· ü¤9.bˆI+vµ˜ÉÐÙ"˜[’FUÏóRŠo;ÃRØñ¬`öFNç”â3#­ŒmL=ƒ šŒ4åPõ%$Ðl¢ÓÉuöÎ * cÝLate+ïr"n꣓uUC›—iظ̀èéJ…›Ç]B\ð kÙΰX5¹P°jØìÃ:ÄÉ)Bˆp(^H˜i%ƒ¹…¯n ZÍükn »‰Ý’É_>N¶§¢‚ Ïœ&3A†32g R4ìÑd«Š Z”8?lçyÒO¤®«‹m'áˆf@çØh¬ÕŒ%sÆžUÊuÅÌ6ë’3iÊ­ Å•¾¹ÛÍ¢Ä%Áp77õš‘DLý^a÷E™9ÍšV\ÑEW ™p¼ vX8)â¬ä4iFf(Må¼aú„}îŽà.ÁÔl?ºFò´,ƒN@6ßÍ 7üwåž3ô½á§ÉEš5ÙF¼îŒ†p JÒ-‹†Ãf‹n1íR^¢¾:ít[½­“BÞY‹ñÔ™dïÁ3‰›©R÷­»­M΄A-µVÔvmê‚ϼ.(SžYìn‘Z¦Q_;¥S™©[ZÌx|Úœ™3ÓK†FR^Ñ9»fpÎUœg ‡sPY‘1O‰ð/ ›}›ëMÑîX k#…ÈãIªRr ÜŠ[ Ü€ ܬFßÁíD¨-Î Æ;¸&-†´Šià{¸OP<ì’Á´1¤MÚ¹ƒt¨†·\Í×ku§¸;5ÙnHXv‘¨!]r1%ûð ‰ Ã0§²Ò„t *‡Ü1½ðT]BØ­¬ÜtáÂ-å†ì”¸!»¤ÇKå2C.—+%‹ã¥Ū2C¾!W*¢or•\Mx´ÖÕ²ZÅm¸…UŒ3W¹¥ªŒqŸ\ÉêlE,rRzüà´…[UÆíºV®3ä[r½!7(GÜ(Wr“|Û›åjCnÁí†Ü*·ò%*ûÜŲ!·ËwYX¹À¤Š2Îu]Ì4y®‚Ó‡Y <'”kïP͆Ü%w't°3²ÝluY9LáÙC¾'÷d˜R‹{23$*Š ¼ƒ0d·\/€!{ä^C¾/÷hrŸ7å†ÜŸÅžkCPáæ™ŸɈ: ù¡\lȃò÷™nÑEñâbÅ`ÈÃ*ÅÆ¹c&{ý>Vi%±¬=f Ù½ƒwbª¼eic›^ÏRĉ^ÒÉw÷w¶`Ö× „OðFŸ3<,ôdn>g?Ü_çy2°Ï|ô—Ÿ˜Þ%õ €Áz¢Xç>"£Nd1ìV“fY72³pLbÊh1s}Á¸½6=£JqŠ®ÒÁÅ´Ï*£É9¡¤ôDuXàU ªëÇ»?›¹²}eÊг¡›§[»Ö‚eN(åóqæ%ÏB=—:‚ñFsWÂú®Áz˱~Œ-)u+Z v{¶©—p,‘|”•¸”ƒÕ{þâî`8žE< ÔrÐ_n¾ÆÛ·¥Cc^¼{[܉բ’z×v„ ç46º˜ð—<[]¿kŒt6SN¶¾oÌÆ;:낊´š»ÔƒÚC“êÕqÖ»›¦[àc¥¡`©ÛÑ|ݽóÛ(~®zvú‚V†7\eŸ £"šzü–¸ª>²ÝL°žP_·k¡°©pÊ6#í‰+Lé€üh¤1I`E™4c…Ò/·]AËin±çع¼™<í±hw׆ڧ0=ëÕ—g3†Nj'"³@#yëYk>nV²0=tziÎH_û»…ò×r3Þ %?~u©'þ\“‡éßv³ÇBä‘‹ Aå ¯Š7ºÝo}m°hüÜ~½ý ÂÇ¡öW„‡sú Èé_&º%î@,yÛÂÝñŽäÅ‘å<ހà MçÙ<ïkéš³®àÇÖ­²0nGRZÕžñA<[1ëQ; hK½&§i13|d³7ØLó]$7$åJ“¦þÙAÈh5㡘ٚz=©ïæ‚ÖÂè´ž¿_‘Ó.Ÿóq*:±€'©ª—£õF†à&kümþñ¹loU}Áõ²ãï¤ášíwùkr9ârÙxÊúám>ß3ÈyÂ⼃­Î˜æâNŽ ›wánöWñ¹})”¬…ìm^YùAh½ðïO ñYL+,ãl"G€íÆ®ß˱‡ý÷ùÇ7<£ÐÜ7hb>Åxú‘×ܽ¹ìiäô!_pÆŒhèÇÈæ² 9€Qå}(à Âðôa´`·¼®F…‚¿Â˜ZoeÀÛ‹±»å±€·°¨ãv£ ã›ë„Æ}r§½8i7N"eÀKâ“{1±Öðõâ\N‚IAUÀ[°°“k|6e)Š­Ðüå¹Èâ=•Lg“iŠÅtªEëñ:¤ðúRt“ŸÄÔ=("õ4RïA~?¦ó(N{,$ªõ)³¾>ÌÈ¡»Æ)Y>gGK;¼åÈ®Õ,JÍ¡¼ª²%TZXÖ‹rE\Þ‹ ÕO¬õü½¨Ü€?i.UÁMjºªÑ[“W”§\Vý æÒ*þRv×ê™"uG¤žYÕxmžì;þâ>,¦.’®®³¾Ó‡ÒKéÖ‘ÍlÜwlfe/N¯ØoEˆŠ§'QÁöÛõb¶ãô Wb#Ó`cm3Æ< ñרŠ_ã"¼KÊ÷° Ÿ£ÇÑ*#`Êx´É)h—btH):åLl—&„evH'¢Ò….¹ËMˆÉ^ÄåQþ~;å9ôÈ˸TÞÄ7äC\)À5ò®“?ãz9†sr˜–*öÅhêåÃp?Û»0à‡=ŒQÖœFÍ {•ºØtw›‚ñt‰c1~DŽÌÎÁ†k½ŒûßUôaŽ ±Ò!Œ¯t0¨F!SE/ÎÜwüƒ²XžÂSýá'æMâ©Íd_ø8ç¼ç¾‘g¦ôMÍ'¡Ð9Íóø×K ô(=SXº-,õ3úð,-Nž¡=ÓÏu†Sá9ÎPôièÔðœ†çÙ Ýñ¦Q³ûc"™áÛ%¹Ïp;°üªs›Ë`q·–x<¿™ð¹à6V¦€¹‹è‹~,¦ï–Ôz*-wÔü‡¼®FŽG|¶˜:%f)ÅXJ¨X–²œBV4[J^ Ï‘²^l×úT{À”€ß œúp…íÃäå¢ÁËÇ÷ü…gÄ9Ü…*Ô+±/#ß’½ZáJNxy½çæ*ú™ŒuìR6+í|*,í&©‘£VäUÀV} 5^ƇbiÌÒ•bY­¼Ð‡5»µùÉ…çyžÇÚæ\µÐ$hêÅ:4¯"iÏù4¥ˆh¹¾pƒm”7ƒ¼ÙãH´ßÌ8¼à‰T ^ÊÈRè”Çx 0ÓK%³°k˜ŸAæg„鵓r7sä~ÆÝ!ÆÙo_ÿŒçðžÇÇœ9Ì`9†Ÿ‹Ž_K_”¼$³ñ²,Á+² ¯J;~);ñšÜ†×å%¼!¯âMyxw§•Ø#){9í%ÊqÇGˆÒÏ2wR»ŸãŒÞ0 /r5{‚—,º5¤ÙËðqÃBH»\ˆ—‰ƒš Öd^pôK¼ÆPΓñ7xY§ñ+òçb–Ü‹7X:y¨ñóx“Yçµ²F园ɯSšü-1ÍÞáï8gËèOÉø{zSe× yµNv½¥a®•`¿aZx¼úà 5Öøm‹èCãd"æQL´ðtLîq:ɛŠeÍc%,3w„…Çä]}sÚêê²Sž}ÆÞÇ~e?š2<‹Ê_¯p媲>lJ¯›0Ý©›j½§ìA^Ãió† uoQ£ÜEûŽ¿Ãø¹pê8Ð~?ÞâžoSÑw/¿å-ýOijw‰g¿ÃÞ¶Kñ/´ÿýžU°üGÒy•^¤}×B±•Œ«‡,ÌZJ¾÷,;–ó–ô;É¢Ÿ½òøxŽb´†f9Ír¶öKêaß2ÿJ|ß‹sð=ijSž8TÞPqhQn§ÈsʃØYQä™Uëµ@Ýë”O~lm.¼ÈI=oÓA`‹2/-ÉõÖìõ'a*oµõ¢}CÓ‘Ms­‡wÅý¨iVÕò„*,ØK¿:&U¦@Äcéå©HbÏ{V5”ky¿†E=ð!Æâ#Þ&³Pÿ==÷ k Oц?âr®]?1Ë>gŽAäù’y{8u cÑaÝ*^®Ž°òHÝ*‡R·Ê!çV¹•g©ò-y‡hÇà·.Ž#¨f€N>Œ©_À{EV43$§øñïøçf®f¯N¹8÷Ѭ·Áœ´;ÙËlçÎdþOü— sNöÃÂù¿]w–aìÌÈûŸÔi‰µ ;{±g<¹Oajaø v<sð[©€Gò¸%.ÇX"œüÐ"ùQ kGOa©XÇg\Ÿp³±ŸÏ>Î>Á¾›â.a¿“ý.ö=¼¸Œ¿Ç³¿œýì¿AÉ#Ù_Éþ›ì¯òäð´;q é¯eéV)^ñåý?PK A‹å>É&org/gradle/cli/ParsedCommandLine.class•Wiwg~FÛÈòÄ‹ê%v“TMc[–å’ñR·Ž›8¶SâÆÆ)-L¬‰¬TÖ(£‘·”.PhÙ¡liÙ7 ’`rX¾Áá;ßøÆŸ œœ˜ç}g$KÖ¸1GçÌÜ÷Î}Ÿ÷Þç.3úǽ?þÀü.‚v˜*òø`Ö!‰‹*¬‚΢ .¶ŠbuŽf¹¸T“¸,.+*žWñBM0#hÄ'ëñ".©øT=^ÂËa¼¢âÕ·}:‚|FÅkt ã³âþ¹0^÷7„ÅçÅÎ/Ô㋸$–__Vñ•0¾A¾V¯ãMaó qù¦¸¼©â[ ͼ1s…#+3¶•É¥©™¼ /ëE;“˜Òóà êf2éœn-CÁÉê§#Î2«çÒÀð¤i¥Ò–žÊ ÙÌÀ“ºU0RãæÒ’žKMfrÆ)yâð(‘ò–Q0r¶£*T>cØ4y¤Zãq ²Œ%sÙH•ŒË¶¥Yéâñ©hªÀ™Ìô¾Mª-°C#™\ÆUЯ08a–~.k ÷Î*Œ›)CxÏð¦‹Kç ë)ñLAtÒ\г³º•kWYçpÎ#„AÍ™ ´¼äÌ FA|»”ÒWÓݳwó/뀽˜)xØÖàÓV5KÔ¶x±  ­:Ô•|)ÜÃö#÷wOP¿cÆÖžcI(ßVñÃ^yØ¢ÈUØ6K¥Þïõ¤ÿbÑ´±\jÂÌú½Óîµ3Tp‘UG(ˆŒí|ÑVÐQ³áH1“M– 7S8–± ´RΊ|{…çY›#Û¶õô·nQ/”ª¬5^kÑKošu·O;=F‚¸m,·RÚÙ¯h¤q3›5$ßbwcÖLOËF¶Ü™mÞÆ ž¶¼ßûLïÐÏn"ªÝöûª9mØG7M”h©z*gJwr‹vè©”DœÕ³Ec úY®ÁeçyíK„?ëa{ÿòÿ?ÂÕœqZ:¯«fy Ó×ÈŒY´ŒcÑöm5ðû…Óã Ý é¸^Xd‹kèÇþšùÞဂ6žŽY–¾"Öð|PÃ[x[Ãwñ= ßÇÄîç1sµ=>4ü?Rñc¶ÂfÖUüDÃOñ3Ýñ8ë*$cÕ/GWý†º*ÀSç.°„5œÂ“~ŽU¾n¶˜ |ɘ†_à—=¦0­a ç4ü WU\Ó0³~ßp´WLöHZÏ–¼:zyÁpyØéøëé*ôÄr¦Kç}j¿Ècü­†ë¸ªážÑð®Ö’[û£v.•”2}Ri›ŒÆ—c9SV´UõPÉPŒIÎir+÷°©9¹¨ã‘´Ž°gËS§¹ Óù´ØQ¥¨Z³yˆœÛ‡ãµàµOBçMkI'Æ GÃ>ýÞ ï=Ÿ7r©‘W=¶~©„Ìœ­gDô­1[¼f(O :º½6y…ÙP=𸟳J8žJe³œÒÇÃü2mç‡sQÑëP0À•ïãš-^^äúЦ5[¼¼þêy?ŒAÊC€2?TjÞH܆’¸ßümøo"@1H1tꆦXG1rõ}kÐL%×°CÁœ¤Ð à¯hœ¾ƒ&Ú4úo!º† ;‚®]LHÒ°e(”ø=¡ŽÀZý˜[]ÿ×êú;7dÃò?‚Æk'W­ü¾o£ÔÎÏñ ùAþØ…ì†=xá5Äð:IáŽCüa£b!ZÀcxœ(DÃÌp1Ž')Å1ÒóaÊïŽæ8¥”÷!°NÎü*’*&TþïÀ=t«hTÑÄÕÐý_éP;É=œ'¼’XœáÙ­/ÚúÚçý F«ÞÆÎ™ù€ë¥bXˆ@Çu #Bw¶vÉp§ú\‡}bÈ9¹Ã»¼‡x?}düÁ©À¨“•¡€àTò}HH.ßÁÝo!’Œîº…ÝsÉèyëV¬ü£«ëOÞÂC×ËIØ/ë¥a$¡qÖa’xA1c¬¾ã¤x‚š)R_J@{ñ& (ü+“}š?Av˜ö3xŠþG¸û f™”«97­ðßCTE§Bºï"¦¢å]t¸Œ”Fœ×.ã'¨ññޜ軅؜ ›t<ìõ2¯i Êâ1éi›³¡ì_3ã%œ‚§ß6ì ;AØ“÷ýžq“÷6ïA-ÁöV4Rg©‘Z¦l G® ¸¶ºþoÿµrNÚ$«gسdyŽE:ú+ŽŽ‘ÉN:$ôËÌ•œèųøxU¡ Í'\ž5øïB%Ç»6 Zç3¾]^ᜈ¢kéô>lšJþ ;ï k>ÚeÞ'*ù6zþ’Eä—Ge¤)ö¤Aù<],;[í¢ÃS;\ô¬Oº†r=l8>Y ŠÃ߀äŠ^ÚÜ9ÙŠÎ ºðNL¯ç‘vczÂÍ­&@S܇¹›Òšg\¬H«VvWc£òŒ .¸ˆ97­{ªXšŘ”ã°—GôoøÛ )ãªÈ‘¶,ÏI8åsöÈQ¦HI 0­›$AbïsŒÖ9ûŠ{ö`_ið–«*^QUt¥ Õ ¢š¹èó‰ñûÏåŒ9%ö<'ì tçE¾B^âKãårÖvs?ê–Ø`ÙÑAé^EAEXP QP –dȹÿPK AyßtÚE,org/gradle/cli/ParsedCommandLineOption.classS]OÔ@=ݯîGeùPa„eQª¨ø"ˆhHV1YƒÁ·awR»í¦íá§ðl⋘¨IÔg”ñN[`Y$ñ¡Ó™Û{Ï=çÜéï?ߘ“4²(ª˜H#‚b ¸ž¦å†ŠÉ$ô4¸™Á-L%q[¾ï¨¸+ßÓr¹§â¾Š [ÌlpWA¶´É¶˜Þð„©—„ëÍ(H•…a1¯áp#-Ÿgƒ³É,C/{ްŒ™9*IÌ Kxs ¢…ñU±E»JÅ%añ—Ú:w^³u“"¹’]aæ*s„<‡Á˜·!ˆI¡d;†n8¬jr½b ýs\^]´k5fU%ÔJݶEí’÷V¥Ý…ñ³”´•=Vy÷‚Õé£ j”;*i=z&xŽÔ «V£Qã–§ §p6K:dÇ)¹ñKn07T@–½U.Û §ÂŸ Évàé“ICzt°]p¶-)kèDNÃ(Æ t]6Mn0“,ñøÒû ÷±(#ÍWmîæ-ÛËo°-žgÖvÞ¿“²Ñ°Š‡f0« ?L'æùZÃôDÝäA¦K©@Ó϶*Õðó$¡Ñÿï)”•õM^!ÛO†îŒ+vï–i¥áÊQ,7_… ”¬ŽÒÀZ~“¾«Â]ªÕ½m\¡(K? LºHï.:EÐ8íÉrZ/PdQÚÙâ>”â!"kûˆ~E쳟}‘Vù_‚*bTÓG;-ÈÇ%ôûø¸bí„XÓÅoˆï¢í‰µœºäÏâÄ"¡Âpú(=@&‚_Ðö¨2êwì%Ž ^*õè$üAêPÀPS÷é°û =1D:¥š!䉩ä¡ÓIfÅ%þÞ±„n‚‰‡05W(v•¾bžRD ×$ÈÄÚ"xÓjÊ2D¬Ï§ìç†xr7‚k> º½!âóV.p£]Á.ÔØG²àÓ±ô€ãDÇ\“Ô8"Ùy Rð‰ŒÿPK A\vÆB| :org/gradle/cli/ProjectPropertiesCommandLineConverter.class’KOÂ@…ÏD|?PâìÀšuã#QŒ+¢$÷C;–1m‡  ÿJW&.üþ(ãª1ØDã,îéœ9ßôv¦oï/¯ö±[@yl汕G)‡ívÙ }FHWkw„LSºœ°Ü!¿]®nY×7ÎZK:Ì¿cJDóØÌ螎ZRy¶§˜ësÛñ…ÝVò;ÚHŸ+-ø )ƒ€…n´kS†#cruLXõøgh|Ó×B†„j­õÀFÌöYèÙ­Dè™èÎè%×LøÜ%”ÖŽñŽ…Ž*‡_‰¨å½?õÖˆ:("‡<Ú„bJÕö ®­ØŠtòfë^*K÷¸Õ ßµ¦ XUÞðV½Œ£Üi01Èk ÂÁp8ƒwZ±ß8T0gî?Pôa¦Î›™m”ŒÎí=ƒžÌC S³s ¦§£‹| Ë1\áôZêq-}CÓ_èJšžEˉèjš™E+ ¨ùw'©õPK A Ï8=|ü9org/gradle/cli/SystemPropertiesCommandLineConverter.class’ËJÃ@†ÏØ«mµ¶ÖjÕEÜ5BPÄ…R/Pé~šÓ‘$&ÓBÞJW‚ À‡'i©AÄYœ3óÏÿÍœ¹¼¼¾Àl— "l¡Y„Íl E Ê<&Ï dÚú@¶ËÇH ÚgÞLÝŠ{:r”Rïs‹:C*X4NĬœ°€ÀQŸ Û´;hZ3a ѽÜG!]îºÔG‹v¹7S"Š5eb o}ɸG ÑÖûtFM‡z¶9‚y¶²¶~X{()spL`7e.°KV, øTXxÉ¢Šõ¿”fDT E¤G ÄPÇWãJm®h~²­Æ49Aíjx­µÑ° ­ÓsÃhöÌ gÔ™¢n8üÇ5©Û] .FÔ’¿s°9õàQˉ΢ⲙ*•sû/@žUg J*æc±e+sƒÊ+1¾ Õî$p¦¾ô´€6¿¡™/t-Í,¢;©h-Í.¢Z ª>kìZÿPK A íAMETA-INF/PK Am±>=@?¤)META-INF/MANIFEST.MFPK AíA›org/PK A íA¿org/gradle/PK AíAêorg/gradle/wrapper/PK A•%Ó¦¹/¤org/gradle/wrapper/BootstrapMainStarter$1.classPK Ai,«$ -¤#org/gradle/wrapper/BootstrapMainStarter.classPK AhQþ}¢Ò#¤org/gradle/wrapper/Download$1.classPK Ay´[À4ØA¤p org/gradle/wrapper/Download$DefaultDownloadProgressListener.classPK Až‚­Û¡~4¤org/gradle/wrapper/Download$ProxyAuthenticator.classPK ApOÌ)¨&!¤öorg/gradle/wrapper/Download.classPK AyL¢¡Ê1¤^$org/gradle/wrapper/DownloadProgressListener.classPK A!9|¶„ 3¤N%org/gradle/wrapper/ExclusiveFileAccessManager.classPK Aì„,y†-¤U,org/gradle/wrapper/GradleUserHomeLookup.classPK APrº™« -*¤/org/gradle/wrapper/GradleWrapperMain.classPK AúâæªÛ"¤ 9org/gradle/wrapper/IDownload.classPK A9lâV†"¤ö9org/gradle/wrapper/Install$1.classPK Aƒgh•|-¤ŒBorg/gradle/wrapper/Install$InstallCheck.classPK A^,ã¡C- ¤SEorg/gradle/wrapper/Install.classPK Að¿:öo4¤2Zorg/gradle/wrapper/Logger.classPK Aé`˜Žï8¤Þ\org/gradle/wrapper/PathAssembler$LocalDistribution.classPK A á¶;+&¤Â^org/gradle/wrapper/PathAssembler.classPK A„ÍëÂ| 0¤ forg/gradle/wrapper/SystemPropertiesHandler.classPK Aª=©°?-¤korg/gradle/wrapper/WrapperConfiguration.classPK AGü¨ (¤norg/gradle/wrapper/WrapperExecutor.classPK AÔe‹ #¤pwgradle-wrapper-classpath.propertiesPK A)¤Ðwgradle-wrapper-parameter-names.propertiesPK AíAxorg/gradle/cli/PK AÕÜ?®<S1¤Hxorg/gradle/cli/AbstractCommandLineConverter.classPK A׃µ³Xì ;¤Ózorg/gradle/cli/AbstractPropertiesCommandLineConverter.classPK A}­ÎyGK1¤„org/gradle/cli/CommandLineArgumentException.classPK A³ßâúg)¤org/gradle/cli/CommandLineConverter.classPK ASf Õg&¤z‚org/gradle/cli/CommandLineOption.classPK Aü튯¥å(¤Àˆorg/gradle/cli/CommandLineParser$1.classPK A$f{K¿ ;¤«‰org/gradle/cli/CommandLineParser$AfterFirstSubCommand.classPK A¥D£¢&3¤Oorg/gradle/cli/CommandLineParser$AfterOptions.classPK AŒ‹Müu <¤Borg/gradle/cli/CommandLineParser$BeforeFirstSubCommand.classPK A´*«ZMïF¤˜”org/gradle/cli/CommandLineParser$CaseInsensitiveStringComparator.classPK A|ìRÎÐ&=¤I—org/gradle/cli/CommandLineParser$KnownOptionParserState.classPK A$ľ¢¥ô<¤tžorg/gradle/cli/CommandLineParser$MissingOptionArgState.classPK ATK>ªÄ=¤s¡org/gradle/cli/CommandLineParser$OptionAwareParserState.classPK A˜%àÌ»ª7¤x¤org/gradle/cli/CommandLineParser$OptionComparator.classPK AäfC˜§£8¤ˆ§org/gradle/cli/CommandLineParser$OptionParserState.classPK A¢Æ÷E«¶3¤…©org/gradle/cli/CommandLineParser$OptionString.classPK AgAq²”x=¤¬org/gradle/cli/CommandLineParser$OptionStringComparator.classPK A`M~U¸2¤p¯org/gradle/cli/CommandLineParser$ParserState.classPK ApÍX Ýk?¤Ä±org/gradle/cli/CommandLineParser$UnknownOptionParserState.classPK A£=l)&¤þ´org/gradle/cli/CommandLineParser.classPK A‹å>É&¤XÇorg/gradle/cli/ParsedCommandLine.classPK AyßtÚE,¤eÏorg/gradle/cli/ParsedCommandLineOption.classPK A\vÆB| :¤‰Òorg/gradle/cli/ProjectPropertiesCommandLineConverter.classPK A Ï8=|ü9¤]Ôorg/gradle/cli/SystemPropertiesCommandLineConverter.classPK44J0Ösmithy-go-1.20.3/codegen/gradle/wrapper/gradle-wrapper.properties000066400000000000000000000003101463735525100250760ustar00rootroot00000000000000distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists smithy-go-1.20.3/codegen/gradlew000077500000000000000000000176061463735525100165210ustar00rootroot00000000000000#!/bin/sh # # Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in # double quotes to make sure that they get re-expanded; and # * put everything else in single quotes, so that it's not re-expanded. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ org.gradle.wrapper.GradleWrapperMain \ "$@" # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" smithy-go-1.20.3/codegen/gradlew.bat000066400000000000000000000053131463735525100172530ustar00rootroot00000000000000@rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega smithy-go-1.20.3/codegen/settings.gradle.kts000066400000000000000000000014351463735525100207570ustar00rootroot00000000000000/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ rootProject.name = "smithy-go" include(":smithy-go-codegen") include(":smithy-go-codegen-test") pluginManagement { repositories { mavenLocal() mavenCentral() gradlePluginPortal() } } smithy-go-1.20.3/codegen/smithy-go-codegen-test/000077500000000000000000000000001463735525100214335ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen-test/build.gradle.kts000066400000000000000000000025721463735525100245200ustar00rootroot00000000000000/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ val smithyVersion: String by project extra["displayName"] = "Smithy :: Go :: Codegen :: Test" extra["moduleName"] = "software.amazon.smithy.go.codegen.test" tasks["jar"].enabled = false buildscript { val smithyVersion: String by project repositories { mavenLocal() mavenCentral() } dependencies { "classpath"("software.amazon.smithy:smithy-cli:$smithyVersion") } } plugins { val smithyGradleVersion: String by project id("software.amazon.smithy") version smithyGradleVersion } repositories { mavenLocal() mavenCentral() } dependencies { implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion") implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion") implementation(project(":smithy-go-codegen")) } smithy-go-1.20.3/codegen/smithy-go-codegen-test/model/000077500000000000000000000000001463735525100225335ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen-test/model/main.smithy000066400000000000000000000251041463735525100247200ustar00rootroot00000000000000$version: "2.0" namespace example.weather use smithy.test#httpRequestTests use smithy.test#httpResponseTests use smithy.waiters#waitable /// Provides weather forecasts. @httpBearerAuth @fakeProtocol @aws.protocols#awsJson1_0 @paginated(inputToken: "nextToken", outputToken: "nextToken", pageSize: "pageSize") service Weather { version: "2006-03-01", resources: [City], operations: [GetCurrentTime, __789BadName] } resource City { identifiers: { cityId: CityId }, read: GetCity, list: ListCities, resources: [Forecast, CityImage], operations: [GetCityAnnouncements] } resource Forecast { identifiers: { cityId: CityId }, read: GetForecast, } resource CityImage { identifiers: { cityId: CityId }, read: GetCityImage, } // "pattern" is a trait. @pattern("^[A-Za-z0-9 ]+$") string CityId @readonly @waitable( CityExists: { description: "Waits until a city has been created", acceptors: [ // Fail-fast if the thing transitions to a "failed" state. { state: "failure", matcher: { errorType: "NoSuchResource" } }, // Fail-fast if the thing transitions to a "failed" state. { state: "failure", matcher: { errorType: "UnModeledError" } }, // Succeed when the city image value is not empty i.e. enters into a "success" state. { state: "success", matcher: { success: true } }, // Retry if city id input is of same length as city name in output { state: "retry", matcher: { inputOutput: { path: "length(input.cityId) == length(output.name)", comparator: "booleanEquals", expected: "true", } } }, // Success if city name in output is seattle { state: "success", matcher: { output: { path: "name", comparator: "stringEquals", expected: "seattle", } } } ] } ) @http(method: "GET", uri: "/cities/{cityId}") operation GetCity { input: GetCityInput, output: GetCityOutput, errors: [NoSuchResource] } @http(method: "POST", uri: "/BadName/{__123abc}") operation __789BadName { input: __BadNameCont, output: __BadNameCont, errors: [NoSuchResource] } // Tests that HTTP protocol tests are generated. apply GetCity @httpRequestTests([ { id: "WriteGetCityAssertions", documentation: "Does something", protocol: "example.weather#fakeProtocol", method: "GET", uri: "/cities/123", body: "", params: { cityId: "123" } } ]) apply GetCity @httpResponseTests([ { id: "WriteGetCityResponseAssertions", documentation: "Does something", protocol: "example.weather#fakeProtocol", code: 200, body: """ { "name": "Seattle", "coordinates": { "latitude": 12.34, "longitude": -56.78 }, "city": { "cityId": "123", "name": "Seattle", "number": "One", "case": "Upper" } }""", bodyMediaType: "application/json", params: { name: "Seattle", coordinates: { latitude: 12.34, longitude: -56.78 }, city: { cityId: "123", name: "Seattle", number: "One", case: "Upper" } } } ]) /// The input used to get a city. structure GetCityInput { // "cityId" provides the identifier for the resource and // has to be marked as required. @required @httpLabel cityId: CityId, } structure __BadNameCont { @required @httpLabel __123abc: String, Member: __456efg, } structure __456efg { __123foo: String, } structure GetCityOutput { // "required" is used on output to indicate if the service // will always provide a value for the member. @required name: String, @required coordinates: CityCoordinates, city: CitySummary, } // This structure is nested within GetCityOutput. structure CityCoordinates { @required latitude: Float, @required longitude: Float, } /// Error encountered when no resource could be found. @error("client") @httpError(404) structure NoSuchResource { /// The type of resource that was not found. @required resourceType: String, message: String, } apply NoSuchResource @httpResponseTests([ { id: "WriteNoSuchResourceAssertions", documentation: "Does something", protocol: "example.weather#fakeProtocol", code: 404, body: """ { "resourceType": "City", "message": "Your custom message" }""", bodyMediaType: "application/json", params: { resourceType: "City", message: "Your custom message" } } ]) // The paginated trait indicates that the operation may // return truncated results. @readonly @paginated(items: "items") @waitable( "ListContainsCity": { description: "Wait until ListCities operation response matches a given state", acceptors: [ // failure in case all items returned match to seattle { state: "failure", matcher: { output: { path: "items[].name", comparator: "allStringEquals", expected: "seattle", } } }, // success in case any items returned match to NewYork { state: "success", matcher: { output: { path: "items[].name", comparator: "anyStringEquals", expected: "NewYork", } } } ] } ) @http(method: "GET", uri: "/cities") operation ListCities { input: ListCitiesInput, output: ListCitiesOutput } apply ListCities @httpRequestTests([ { id: "WriteListCitiesAssertions", documentation: "Does something", protocol: "example.weather#fakeProtocol", method: "GET", uri: "/cities", body: "", queryParams: ["pageSize=50"], forbidQueryParams: ["nextToken"], params: { pageSize: 50 } } ]) integer DefaultInteger boolean DefaultBool structure ListCitiesInput { @httpQuery("nextToken") nextToken: String, @httpQuery("aString") aString: String, @httpQuery("defaultBool") defaultBool: DefaultBool, @httpQuery("boxedBool") boxedBool: Boolean, @httpQuery("defaultNumber") defaultNumber: DefaultInteger, @httpQuery("boxedNumber") boxedNumber: Integer, @httpQuery("someEnum") someEnum: SimpleYesNo, @httpQuery("pageSize") pageSize: Integer } intEnum SimpleOneZero { ONE = 1 ZERO = 0 } @mixin structure ListCitiesMixin { someEnum: SimpleYesNo, aString: String, defaultBool: DefaultBool, boxedBool: Boolean, defaultNumber: DefaultInteger, boxedNumber: Integer, someIntegerEnum: SimpleOneZero } structure ListCitiesOutput with [ListCitiesMixin] { nextToken: String, @required items: CitySummaries, sparseItems: SparseCitySummaries, } // CitySummaries is a list of CitySummary structures. list CitySummaries { member: CitySummary } // CitySummaries is a sparse list of CitySummary structures. @sparse list SparseCitySummaries { member: CitySummary } // CitySummary contains a reference to a City. @references([{resource: City}]) structure CitySummary { @required cityId: CityId, @required name: String, number: String, case: String, } @readonly @http(method: "GET", uri: "/current-time") operation GetCurrentTime { output: GetCurrentTimeOutput } structure GetCurrentTimeOutput { @required time: Timestamp } @readonly @http(method: "GET", uri: "/cities/{cityId}/forecast") operation GetForecast { input: GetForecastInput, output: GetForecastOutput } // "cityId" provides the only identifier for the resource since // a Forecast doesn't have its own. structure GetForecastInput { @required @httpLabel cityId: CityId, } structure GetForecastOutput { chanceOfRain: Float, precipitation: Precipitation, } union Precipitation { rain: Boolean, sleet: Boolean, hail: StringMap, snow: SimpleYesNo, mixed: TypedYesNo, other: OtherStructure, blob: Blob, foo: example.weather.nested#Foo, baz: example.weather.nested.more#Baz, } structure OtherStructure {} enum SimpleYesNo { YES NO } enum TypedYesNo { YES = "YES" NO = "NO" } map StringMap { key: String, value: String, } @readonly @http(method: "POST", uri: "/cities/{cityId}/image") operation GetCityImage { input: GetCityImageInput, output: GetCityImageOutput, errors: [NoSuchResource] } structure GetCityImageInput { @required @httpLabel cityId: CityId, @required imageType: ImageType, } union ImageType { raw: DefaultBool, png: PNGImage, } structure PNGImage { @required height: Integer, @required width: Integer, } structure GetCityImageOutput { @httpPayload image: CityImageData = "", } @streaming blob CityImageData @readonly @http(method: "GET", uri: "/cities/{cityId}/announcements") operation GetCityAnnouncements { input: GetCityAnnouncementsInput, output: GetCityAnnouncementsOutput, errors: [NoSuchResource] } structure GetCityAnnouncementsInput { @required @httpLabel cityId: CityId, } structure GetCityAnnouncementsOutput { @httpHeader("x-last-updated") lastUpdated: Timestamp, @httpPayload announcements: Announcements } @streaming union Announcements { police: Message, fire: Message, health: Message } structure Message { message: String, author: String } // Define a fake protocol trait for use. @trait @protocolDefinition structure fakeProtocol {} smithy-go-1.20.3/codegen/smithy-go-codegen-test/model/more-nesting.smithy000066400000000000000000000001461463735525100264020ustar00rootroot00000000000000$version: "2" namespace example.weather.nested.more structure Baz { baz: String, bar: String, } smithy-go-1.20.3/codegen/smithy-go-codegen-test/model/nested.smithy000066400000000000000000000001411463735525100252500ustar00rootroot00000000000000$version: "2" namespace example.weather.nested structure Foo { baz: String, bar: String, } smithy-go-1.20.3/codegen/smithy-go-codegen-test/smithy-build.json000066400000000000000000000011301463735525100247330ustar00rootroot00000000000000{ "version": "1.0", "plugins": { "go-codegen": { "service": "example.weather#Weather", "module": "github.com/aws/smithy-go/internal/tests/service/weather", "moduleVersion": "0.0.1", "generateGoMod": true, "goDirective": "1.18" }, "go-server-codegen": { "service": "example.weather#Weather", "module": "github.com/aws/smithy-go/internal/tests/server/weather", "moduleVersion": "0.0.1", "generateGoMod": true, "goDirective": "1.18" } } } smithy-go-1.20.3/codegen/smithy-go-codegen/000077500000000000000000000000001463735525100204565ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/build.gradle.kts000066400000000000000000000024551463735525100235430ustar00rootroot00000000000000/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ val smithyVersion: String by project description = "Generates Go code from Smithy models" extra["displayName"] = "Smithy :: Go :: Codegen" extra["moduleName"] = "software.amazon.smithy.go.codegen" dependencies { api("software.amazon.smithy:smithy-codegen-core:$smithyVersion") api("software.amazon.smithy:smithy-aws-traits:$smithyVersion") implementation("software.amazon.smithy:smithy-waiters:$smithyVersion") api("com.atlassian.commonmark:commonmark:0.15.2") api("org.jsoup:jsoup:1.14.1") api("software.amazon.smithy:smithy-rules-engine:$smithyVersion") implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion") api("software.amazon.smithy:smithy-protocol-traits:$smithyVersion") } smithy-go-1.20.3/codegen/smithy-go-codegen/src/000077500000000000000000000000001463735525100212455ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/000077500000000000000000000000001463735525100221715ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/000077500000000000000000000000001463735525100231125ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/000077500000000000000000000000001463735525100247445ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/000077500000000000000000000000001463735525100262315ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/000077500000000000000000000000001463735525100275465ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/000077500000000000000000000000001463735525100301535ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/000077500000000000000000000000001463735525100315575ustar00rootroot00000000000000AddOperationShapes.java000066400000000000000000000115351463735525100360650ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.TreeSet; import java.util.logging.Logger; import software.amazon.smithy.go.codegen.trait.BackfilledInputOutputTrait; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.AbstractShapeBuilder; import software.amazon.smithy.model.shapes.OperationShape; 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; /** * Ensures that each operation has a unique input and output shape. */ public final class AddOperationShapes { private static final Logger LOGGER = Logger.getLogger(AddOperationShapes.class.getName()); private AddOperationShapes() { } /** * Processes the given model and returns a new model ensuring service operation has an unique input and output * synthesized shape. * * @param model the model * @param serviceShapeId the service shape * @return a model with unique operation input and output shapes */ public static Model execute(Model model, ShapeId serviceShapeId) { TopDownIndex topDownIndex = model.getKnowledge(TopDownIndex.class); ServiceShape service = model.expectShape(serviceShapeId, ServiceShape.class); TreeSet operations = new TreeSet<>(topDownIndex.getContainedOperations( model.expectShape(serviceShapeId))); Model.Builder modelBuilder = model.toBuilder(); for (OperationShape operation : operations) { ShapeId operationId = operation.getId(); LOGGER.info(() -> "building unique input/output shapes for " + operationId); StructureShape newInputShape = operation.getInput() .map(shapeId -> cloneOperationShape( service, operationId, (StructureShape) model.expectShape(shapeId), "Input")) .orElseGet(() -> emptyOperationStructure(service, operationId, "Input")); StructureShape newOutputShape = operation.getOutput() .map(shapeId -> cloneOperationShape( service, operationId, (StructureShape) model.expectShape(shapeId), "Output")) .orElseGet(() -> emptyOperationStructure(service, operationId, "Output")); // Add new input/output to model modelBuilder.addShape(newInputShape); modelBuilder.addShape(newOutputShape); // Update operation model with the input/output shape ids modelBuilder.addShape(operation.toBuilder() .input(newInputShape.toShapeId()) .output(newOutputShape.toShapeId()) .build()); } return modelBuilder.build(); } private static StructureShape emptyOperationStructure(ServiceShape service, ShapeId opShapeId, String suffix) { return StructureShape.builder() .id(ShapeId.fromParts(CodegenUtils.getSyntheticTypeNamespace(), opShapeId.getName(service) + suffix)) .addTrait(Synthetic.builder().build()) .addTrait(new BackfilledInputOutputTrait()) .build(); } private static StructureShape cloneOperationShape( ServiceShape service, ShapeId operationShapeId, StructureShape structureShape, String suffix ) { return (StructureShape) cloneShape(structureShape, operationShapeId.getName(service) + suffix); } private static Shape cloneShape(Shape shape, String cloneShapeName) { ShapeId cloneShapeId = ShapeId.fromParts(CodegenUtils.getSyntheticTypeNamespace(), cloneShapeName); AbstractShapeBuilder builder = Shape.shapeToBuilder(shape) .id(cloneShapeId) .addTrait(Synthetic.builder() .archetype(shape.getId()) .build()); shape.members().forEach(memberShape -> { builder.addMember(memberShape.toBuilder() .id(cloneShapeId.withMember(memberShape.getMemberName())) .build()); }); return (Shape) builder.build(); } } ApplicationProtocol.java000066400000000000000000000067351463735525100363430ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.Objects; import software.amazon.smithy.codegen.core.Symbol; /** * Represents the resolves {@link Symbol}s and references for an * application protocol (e.g., "http", "mqtt", etc). */ public final class ApplicationProtocol { private final String name; private final Symbol requestType; private final Symbol responseType; /** * Creates a resolved application protocol. * * @param name The protocol name (e.g., http, mqtt, etc). * @param requestType The type used to represent request messages for the protocol. * @param responseType The type used to represent response messages for the protocol. */ public ApplicationProtocol( String name, Symbol requestType, Symbol responseType ) { this.name = name; this.requestType = requestType; this.responseType = responseType; } /** * Creates a default HTTP application protocol. * * @return Returns the created application protocol. */ public static ApplicationProtocol createDefaultHttpApplicationProtocol() { return new ApplicationProtocol( "http", createHttpSymbol("Request"), createHttpSymbol("Response") ); } private static Symbol createHttpSymbol(String symbolName) { return SymbolUtils.createPointableSymbolBuilder(symbolName, SmithyGoDependency.SMITHY_HTTP_TRANSPORT) .build(); } /** * Gets the protocol name. * *

All HTTP protocols should start with "http". * All MQTT protocols should start with "mqtt". * * @return Returns the protocol name. */ public String getName() { return name; } /** * Checks if the protocol is an HTTP based protocol. * * @return Returns true if it is HTTP based. */ public boolean isHttpProtocol() { return getName().startsWith("http"); } /** * Gets the symbol used to refer to the request type for this protocol. * * @return Returns the protocol request type. */ public Symbol getRequestType() { return requestType; } /** * Gets the symbol used to refer to the response type for this protocol. * * @return Returns the protocol response type. */ public Symbol getResponseType() { return responseType; } @Override public boolean equals(Object o) { if (this == o) { return true; } else if (!(o instanceof ApplicationProtocol)) { return false; } ApplicationProtocol that = (ApplicationProtocol) o; return requestType.equals(that.requestType) && responseType.equals(that.responseType); } @Override public int hashCode() { return Objects.hash(requestType, responseType); } } ClientOptions.java000066400000000000000000000227711463735525100351460ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.function.Predicate; import java.util.stream.Collectors; import software.amazon.smithy.go.codegen.auth.AuthSchemeResolverGenerator; import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; import software.amazon.smithy.go.codegen.integration.ConfigField; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.integration.auth.AnonymousDefinition; import software.amazon.smithy.model.knowledge.ServiceIndex; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.traits.synthetic.NoAuthTrait; import software.amazon.smithy.utils.MapUtils; /** * Implements codegen for service client config. */ public class ClientOptions implements GoWriter.Writable { public static final String NAME = "Options"; private final ProtocolGenerator.GenerationContext context; private final ApplicationProtocol protocol; private final List fields; private final Map authSchemes; public ClientOptions(ProtocolGenerator.GenerationContext context, ApplicationProtocol protocol) { this.context = context; this.protocol = protocol; this.fields = context.getIntegrations().stream() .flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream()) .flatMap(it -> it.getConfigFields().stream()) .distinct() .sorted(Comparator.comparing(ConfigField::getName)) .toList(); this.authSchemes = context.getIntegrations().stream() .flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream()) .flatMap(it -> it.getAuthSchemeDefinitions().entrySet().stream()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override public void accept(GoWriter writer) { writer.write(generate()); } private GoWriter.Writable generate() { var apiOptionsDocs = goDocTemplate( "Set of options to modify how an operation is invoked. These apply to all operations " + "invoked for this client. Use functional options on operation call to modify this " + "list for per operation behavior." ); return goTemplate(""" $protocolTypes:W type $options:L struct { $apiOptionsDocs:W APIOptions []func($stack:P) error $fields:W $protocolFields:W } $copy:W $getIdentityResolver:W $helpers:W """, MapUtils.of( "protocolTypes", generateProtocolTypes(), "apiOptionsDocs", apiOptionsDocs, "options", NAME, "stack", SmithyGoTypes.Middleware.Stack, "fields", GoWriter.ChainWritable.of(fields.stream().map(this::writeField).toList()).compose(), "protocolFields", generateProtocolFields(), "copy", generateCopy(), "getIdentityResolver", generateGetIdentityResolver(), "helpers", generateHelpers() )); } private GoWriter.Writable generateProtocolTypes() { ensureSupportedProtocol(); return goTemplate(""" type HTTPClient interface { Do($P) ($P, error) } """, GoStdlibTypes.Net.Http.Request, GoStdlibTypes.Net.Http.Response); } private GoWriter.Writable writeField(ConfigField field) { GoWriter.Writable docs = writer -> { field.getDocumentation().ifPresent(writer::writeDocs); field.getDeprecated().ifPresent(s -> { if (field.getDocumentation().isPresent()) { writer.writeDocs(""); } writer.writeDocs(String.format("Deprecated: %s", s)); }); }; return goTemplate(""" $W $L $P """, docs, field.getName(), field.getType()); } private GoWriter.Writable generateProtocolFields() { ensureSupportedProtocol(); return goTemplate(""" $1W HTTPClient HTTPClient $2W AuthSchemeResolver $4L $3W AuthSchemes []$5T """, goDocTemplate("The HTTP client to invoke API calls with. " + "Defaults to client's default HTTP implementation if nil."), goDocTemplate("The auth scheme resolver which determines how to authenticate for each operation."), goDocTemplate("The list of auth schemes supported by the client."), AuthSchemeResolverGenerator.INTERFACE_NAME, SmithyGoTypes.Transport.Http.AuthScheme); } private GoWriter.Writable generateCopy() { return goTemplate(""" // Copy creates a clone where the APIOptions list is deep copied. func (o $1L) Copy() $1L { to := o to.APIOptions = make([]func($2P) error, len(o.APIOptions)) copy(to.APIOptions, o.APIOptions) return to } """, NAME, SmithyGoTypes.Middleware.Stack); } private GoWriter.Writable generateGetIdentityResolver() { return goTemplate(""" func (o $L) GetIdentityResolver(schemeID string) $T { $W $W return nil } """, NAME, SmithyGoTypes.Auth.IdentityResolver, GoWriter.ChainWritable.of( ServiceIndex.of(context.getModel()) .getEffectiveAuthSchemes(context.getService()).keySet().stream() .filter(authSchemes::containsKey) .map(trait -> generateGetIdentityResolverMapping(trait, authSchemes.get(trait))) .toList() ).compose(false), generateGetIdentityResolverMapping(NoAuthTrait.ID, new AnonymousDefinition())); } private GoWriter.Writable generateGetIdentityResolverMapping(ShapeId schemeId, AuthSchemeDefinition scheme) { return goTemplate(""" if schemeID == $S { return $W }""", schemeId.toString(), scheme.generateOptionsIdentityResolver()); } private GoWriter.Writable generateHelpers() { return writer -> { writer.write(""" $W func WithAPIOptions(optFns ...func($P) error) func(*Options) { return func (o *Options) { o.APIOptions = append(o.APIOptions, optFns...) } } """, goDocTemplate( "WithAPIOptions returns a functional option for setting the Client's APIOptions option." ), SmithyGoTypes.Middleware.Stack); fields.stream().filter(ConfigField::getWithHelper).filter(ConfigField::isDeprecated) .forEach(configField -> { writer.writeDocs(configField.getDeprecated().get()); writeHelper(writer, configField); }); fields.stream().filter(ConfigField::getWithHelper).filter(Predicate.not(ConfigField::isDeprecated)) .forEach(configField -> { writer.writeDocs( String.format( "With%s returns a functional option for setting the Client's %s option.", configField.getName(), configField.getName())); writeHelper(writer, configField); }); }; } private void writeHelper(GoWriter writer, ConfigField configField) { writer.write(""" func With$1L(v $2P) func(*Options) { return func(o *Options) { o.$1L = v } } """, configField.getName(), configField.getType()); } private void ensureSupportedProtocol() { if (!protocol.isHttpProtocol()) { throw new UnsupportedOperationException("Protocols other than HTTP are not yet implemented: " + protocol); } } } CodegenUtils.java000066400000000000000000000356031463735525100347370ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import java.util.logging.Logger; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.CollectionShape; import software.amazon.smithy.model.shapes.MapShape; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.traits.RequiredTrait; import software.amazon.smithy.model.traits.TitleTrait; import software.amazon.smithy.utils.StringUtils; /** * Utility methods likely to be needed across packages. */ public final class CodegenUtils { private static final Logger LOGGER = Logger.getLogger(CodegenUtils.class.getName()); private static final String SYNTHETIC_NAMESPACE = "smithy.go.synthetic"; private CodegenUtils() { } /** * Executes a given shell command in a given directory. * * @param command The string command to execute, e.g. "go fmt". * @param directory The directory to run the command in. * @return Returns the console output of the command. */ public static String runCommand(String command, Path directory) { String[] finalizedCommand; if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { finalizedCommand = new String[]{"cmd.exe", "/c", command}; } else { finalizedCommand = new String[]{"sh", "-c", command}; } ProcessBuilder processBuilder = new ProcessBuilder(finalizedCommand) .redirectErrorStream(true) .directory(directory.toFile()); try { Process process = processBuilder.start(); List output = new ArrayList<>(); // Capture output for reporting. try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader( process.getInputStream(), Charset.defaultCharset()))) { String line; while ((line = bufferedReader.readLine()) != null) { LOGGER.finest(line); output.add(line); } } process.waitFor(); process.destroy(); String joinedOutput = String.join(System.lineSeparator(), output); if (process.exitValue() != 0) { throw new CodegenException(String.format( "Command `%s` failed with output:%n%n%s", command, joinedOutput)); } return joinedOutput; } catch (InterruptedException | IOException e) { throw new CodegenException(e); } } /** * Gets the name under which the given package will be exported by default. * * @param packageName The full package name of the exported package. * @return The name a the package will be imported under by default. */ public static String getDefaultPackageImportName(String packageName) { if (StringUtils.isBlank(packageName) || !packageName.contains("/")) { return packageName; } return packageName.substring(packageName.lastIndexOf('/') + 1); } /** * Gets the alias to use when referencing the given symbol outside of its namespace. * *

The default value is the last path component of the symbol's namespace. * * @param symbol The symbol whose whose namespace alias should be retrieved. * @return The alias of the symbol's namespace. */ public static String getSymbolNamespaceAlias(Symbol symbol) { return symbol.getProperty(SymbolUtils.NAMESPACE_ALIAS, String.class) .filter(StringUtils::isNotBlank) .orElse(CodegenUtils.getDefaultPackageImportName(symbol.getNamespace())); } /** * Detects if an annotated mediatype indicates JSON contents. * * @param mediaType The media type to inspect. * @return If the media type indicates JSON contents. */ public static boolean isJsonMediaType(String mediaType) { return mediaType.equals("application/json") || mediaType.endsWith("+json"); } /** * Get the namespace where synthetic types are generated at runtime. * * @return synthetic type namespace */ public static String getSyntheticTypeNamespace() { return CodegenUtils.SYNTHETIC_NAMESPACE; } /** * Get if the passed in shape is decorated as a synthetic clone, but there is no other shape the clone is * created from. * * @param shape the shape to check if its a stubbed synthetic clone without an archetype. * @return if the shape is synthetic clone, but not based on a specific shape. */ public static boolean isStubSynthetic(Shape shape) { Optional optional = shape.getTrait(Synthetic.class); if (!optional.isPresent()) { return false; } Synthetic synth = optional.get(); return !synth.getArchetype().isPresent(); } /** * Returns the operand decorated with an & if the address of the shape type can be taken. * * @param model API model reference * @param pointableIndex pointable index * @param shape shape to use * @param operand value to decorate * @return updated operand */ public static String asAddressIfAddressable( Model model, GoPointableIndex pointableIndex, Shape shape, String operand ) { boolean isStruct = shape.getType() == ShapeType.STRUCTURE; if (shape.isMemberShape()) { isStruct = model.expectShape(shape.asMemberShape().get().getTarget()).getType() == ShapeType.STRUCTURE; } boolean shouldAddress = pointableIndex.isPointable(shape) && isStruct; return shouldAddress ? "&" + operand : operand; } /** * Returns the operand decorated with an "*" if the shape is dereferencable. * * @param pointableIndex knowledge index for if shape is pointable. * @param shape The shape whose value needs to be read. * @param operand The value to be read from. * @return updated operand */ public static String getAsValueIfDereferencable( GoPointableIndex pointableIndex, Shape shape, String operand ) { if (!pointableIndex.isDereferencable(shape)) { return operand; } return '*' + operand; } /** * Returns the operand decorated as a pointer type, without creating double pointer. * * @param pointableIndex knowledge index for if shape is pointable. * @param shape The shape whose value of the type. * @param operand The value to read. * @return updated operand */ public static String getTypeAsTypePointer( GoPointableIndex pointableIndex, Shape shape, String operand ) { if (pointableIndex.isPointable(shape)) { return operand; } return '*' + operand; } /** * Get the pointer reference to operand , if symbol is pointable. * This method can be used by deserializers to get pointer to * operand. * * @param model model for api. * @param writer The writer dependencies will be added to, if needed. * @param pointableIndex knowledge index for if shape is pointable. * @param shape The shape whose value needs to be assigned. * @param operand The Operand is the value to be assigned to the symbol shape. * @return The Operand, along with pointer reference if applicable */ public static String getAsPointerIfPointable( Model model, GoWriter writer, GoPointableIndex pointableIndex, Shape shape, String operand ) { if (!pointableIndex.isPointable(shape)) { return operand; } if (shape.isMemberShape()) { shape = model.expectShape(shape.asMemberShape().get().getTarget()); } String prefix = ""; String suffix = ")"; switch (shape.getType()) { case STRING: prefix = "ptr.String("; break; case BOOLEAN: prefix = "ptr.Bool("; break; case BYTE: prefix = "ptr.Int8("; break; case SHORT: prefix = "ptr.Int16("; break; case INTEGER: prefix = "ptr.Int32("; break; case LONG: prefix = "ptr.Int64("; break; case FLOAT: prefix = "ptr.Float32("; break; case DOUBLE: prefix = "ptr.Float64("; break; case TIMESTAMP: prefix = "ptr.Time("; break; default: return '&' + operand; } writer.addUseImports(SmithyGoDependency.SMITHY_PTR); return prefix + operand + suffix; } /** * Returns the shape unpacked as a CollectionShape. Throws and exception if the passed in * shape is not a list or set. * * @param shape the list or set shape. * @return The unpacked CollectionShape. */ public static CollectionShape expectCollectionShape(Shape shape) { if (shape instanceof CollectionShape) { return (CollectionShape) (shape); } throw new CodegenException("expect shape " + shape.getId() + " to be Collection, was " + shape.getType()); } /** * Returns the shape unpacked as a MapShape. Throws and exception if the passed in * shape is not a map. * * @param shape the map shape. * @return The unpacked MapShape. */ public static MapShape expectMapShape(Shape shape) { if (shape instanceof MapShape) { return (MapShape) (shape); } throw new CodegenException("expect shape " + shape.getId() + " to be Map, was " + shape.getType()); } /** * Comparator to sort ShapeMember lists alphabetically, with required members first followed by optional members. */ public static final class SortedMembers implements Comparator { private final SymbolProvider symbolProvider; /** * Initializes the SortedMembers. * * @param symbolProvider symbol provider used for codegen. */ public SortedMembers(SymbolProvider symbolProvider) { this.symbolProvider = symbolProvider; } @Override public int compare(MemberShape a, MemberShape b) { // first compare if the members are required or not, which ever member is required should win. If both // members are required or not required, continue on to alphabetic search. // If a is required but b isn't return -1 so a is sorted before b // If b is required but a isn't, return 1 so a is sorted after b // If both a and b are required or optional, use alphabetic sorting of a and b's member name. int requiredMember = 0; if (a.hasTrait(RequiredTrait.class)) { requiredMember -= 1; } if (b.hasTrait(RequiredTrait.class)) { requiredMember += 1; } if (requiredMember != 0) { return requiredMember; } return symbolProvider.toMemberName(a) .compareTo(symbolProvider.toMemberName(b)); } } /** * Attempts to find the first member by exact name in the containing structure. If the member is not found an * exception will be thrown. * * @param shape structure containing member * @param name member name * @return MemberShape if found */ public static MemberShape expectMember(StructureShape shape, String name) { return expectMember(shape, name::equals); } /** * Attempts to find the first member by name using a member name predicate in the containing structure. If the * member is not found an exception will be thrown. * * @param shape structure containing member * @param memberNamePredicate member name to search for * @return MemberShape if found */ public static MemberShape expectMember(StructureShape shape, Predicate memberNamePredicate) { return shape.getAllMembers().values().stream() .filter((p) -> memberNamePredicate.test(p.getMemberName())) .findFirst() .orElseThrow(() -> new CodegenException("did not find member in structure shape, " + shape.getId())); } /** * Attempts to get the title of the API's service from the model. If unalbe to get the title the fallback value * will be returned instead. * * @param shape service shape * @param fallback string to return if service does not have a title * @return title of service */ public static String getServiceTitle(ServiceShape shape, String fallback) { return shape.getTrait(TitleTrait.class).map(TitleTrait::getValue).orElse(fallback); } /** * isNumber returns if the shape is a number shape. * * @param shape shape to check * @return true if is a number shape. */ public static boolean isNumber(Shape shape) { switch (shape.getType()) { case BYTE: case SHORT: case INTEGER: case INT_ENUM: case LONG: case FLOAT: case DOUBLE: return true; default: return false; } } } CodegenVisitor.java000066400000000000000000000414231463735525100352730ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.ServiceLoader; import java.util.Set; import java.util.TreeSet; import java.util.logging.Logger; import java.util.stream.Collectors; import software.amazon.smithy.build.FileManifest; import software.amazon.smithy.build.PluginContext; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolDependency; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoSettings.ArtifactType; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.ServiceIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.neighbor.Walker; import software.amazon.smithy.model.shapes.IntEnumShape; import software.amazon.smithy.model.shapes.OperationShape; 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.ShapeVisitor; 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.transform.ModelTransformer; import software.amazon.smithy.utils.OptionalUtils; import software.amazon.smithy.utils.SmithyInternalApi; /** * Orchestrates Go client generation. */ @SmithyInternalApi final class CodegenVisitor extends ShapeVisitor.Default { private static final Logger LOGGER = Logger.getLogger(CodegenVisitor.class.getName()); private final GoSettings settings; private final Model model; private final Model modelWithoutTraitShapes; private final ServiceShape service; private final FileManifest fileManifest; private final SymbolProvider symbolProvider; private final GoDelegator writers; private final List integrations = new ArrayList<>(); private final ProtocolGenerator protocolGenerator; private final ApplicationProtocol applicationProtocol; private final List runtimePlugins = new ArrayList<>(); private final ProtocolDocumentGenerator protocolDocumentGenerator; private final EventStreamGenerator eventStreamGenerator; private final GoCodegenContext ctx; CodegenVisitor(PluginContext context) { // Load all integrations. ClassLoader loader = context.getPluginClassLoader().orElse(getClass().getClassLoader()); LOGGER.info("Attempting to discover GoIntegration from the classpath..."); ServiceLoader.load(GoIntegration.class, loader) .forEach(integration -> { if (integration.getArtifactType().equals(ArtifactType.CLIENT)) { LOGGER.info(() -> "Adding GoIntegration: " + integration.getClass().getName()); integrations.add(integration); } }); integrations.sort(Comparator.comparingInt(GoIntegration::getOrder)); settings = GoSettings.from(context.getSettings()); fileManifest = context.getFileManifest(); Model resolvedModel = context.getModel(); var modelTransformer = ModelTransformer.create(); /* * smithy 1.23.0 added support for mixins. This transform flattens and applies * the mixins * and remove them from the model */ resolvedModel = modelTransformer.flattenAndRemoveMixins(resolvedModel); // Add unique operation input/output shapes resolvedModel = AddOperationShapes.execute(resolvedModel, settings.getService()); /* * smithy 1.12.0 added support for binding common errors to the service shape * this transform copies these common errors to the operations */ resolvedModel = modelTransformer.copyServiceErrorsToOperations(resolvedModel, settings.getService(resolvedModel)); LOGGER.info(() -> "Preprocessing smithy model"); for (GoIntegration goIntegration : integrations) { resolvedModel = goIntegration.preprocessModel(resolvedModel, settings); } model = resolvedModel; // process final model integrations.forEach(integration -> { integration.processFinalizedModel(settings, model); }); // fetch runtime plugins integrations.forEach(integration -> { integration.getClientPlugins().forEach(runtimePlugin -> { LOGGER.info(() -> "Adding Go runtime plugin: " + runtimePlugin); runtimePlugins.add(runtimePlugin); }); }); modelWithoutTraitShapes = modelTransformer.getModelWithoutTraitShapes(model); service = settings.getService(model); LOGGER.info(() -> "Generating Go client for service " + service.getId()); SymbolProvider resolvedProvider = GoCodegenPlugin.createSymbolProvider(model, settings); for (GoIntegration integration : integrations) { resolvedProvider = integration.decorateSymbolProvider(settings, model, resolvedProvider); } symbolProvider = resolvedProvider; protocolGenerator = resolveProtocolGenerator(integrations, model, service, settings); applicationProtocol = protocolGenerator == null ? ApplicationProtocol.createDefaultHttpApplicationProtocol() : protocolGenerator.getApplicationProtocol(); writers = new GoDelegator(fileManifest, symbolProvider); protocolDocumentGenerator = new ProtocolDocumentGenerator(settings, model, writers); this.eventStreamGenerator = new EventStreamGenerator(settings, model, writers, symbolProvider, service); this.ctx = new GoCodegenContext(model, settings, symbolProvider, fileManifest, writers, integrations); } private static ProtocolGenerator resolveProtocolGenerator( Collection integrations, Model model, ServiceShape service, GoSettings settings) { // Collect all the supported protocol generators. Map generators = new HashMap<>(); for (GoIntegration integration : integrations) { for (ProtocolGenerator generator : integration.getProtocolGenerators()) { generators.put(generator.getProtocol(), generator); } } ServiceIndex serviceIndex = ServiceIndex.of(model); ShapeId protocolTrait; try { protocolTrait = settings.resolveServiceProtocol(serviceIndex, service, generators.keySet()); settings.setProtocol(protocolTrait); } catch (UnresolvableProtocolException e) { LOGGER.warning("Unable to find a protocol generator for " + service.getId() + ": " + e.getMessage()); protocolTrait = null; } return protocolTrait != null ? generators.get(protocolTrait) : null; } void execute() { // Generate models that are connected to the service being generated. LOGGER.fine("Walking shapes from " + service.getId() + " to find shapes to generate"); Set serviceShapes = new TreeSet<>(new Walker(modelWithoutTraitShapes).walkShapes(service)); for (Shape shape : serviceShapes) { shape.accept(this); } // Generate any required types and functions need to support protocol documents. protocolDocumentGenerator.generateDocumentSupport(); // Generate a struct to handle unknown tags in unions List unions = serviceShapes.stream() .map(Shape::asUnionShape) .flatMap(OptionalUtils::stream) .collect(Collectors.toList()); if (!unions.isEmpty()) { writers.useShapeWriter(unions.get(0), writer -> { UnionGenerator.generateUnknownUnion(writer, unions, symbolProvider); }); } for (GoIntegration integration : integrations) { integration.writeAdditionalFiles(settings, model, symbolProvider, writers::useFileWriter); integration.writeAdditionalFiles(settings, model, symbolProvider, writers); integration.writeAdditionalFiles(ctx); } eventStreamGenerator.generateEventStreamInterfaces(); TopDownIndex.of(model).getContainedOperations(service) .forEach(eventStreamGenerator::generateOperationEventStreamStructure); if (protocolGenerator != null) { LOGGER.info("Generating serde for protocol " + protocolGenerator.getProtocol() + " on " + service.getId()); ProtocolGenerator.GenerationContext.Builder contextBuilder = ProtocolGenerator.GenerationContext.builder() .protocolName(protocolGenerator.getProtocolName()) .integrations(integrations) .model(model) .service(service) .settings(settings) .symbolProvider(symbolProvider) .delegator(writers); LOGGER.info("Generating serde for protocol " + protocolGenerator.getProtocol() + " on " + service.getId()); writers.useFileWriter("serializers.go", settings.getModuleName(), writer -> { ProtocolGenerator.GenerationContext context = contextBuilder.writer(writer).build(); protocolGenerator.generateRequestSerializers(context); protocolGenerator.generateSharedSerializerComponents(context); }); writers.useFileWriter("deserializers.go", settings.getModuleName(), writer -> { ProtocolGenerator.GenerationContext context = contextBuilder.writer(writer).build(); protocolGenerator.generateResponseDeserializers(context); protocolGenerator.generateSharedDeserializerComponents(context); }); if (eventStreamGenerator.hasEventStreamOperations()) { eventStreamGenerator.writeEventStreamImplementation(writer -> { ProtocolGenerator.GenerationContext context = contextBuilder.writer(writer).build(); protocolGenerator.generateEventStreamComponents(context); }); } writers.useFileWriter("endpoints.go", settings.getModuleName(), writer -> { ProtocolGenerator.GenerationContext context = contextBuilder.writer(writer).build(); protocolGenerator.generateEndpointResolution(context); }); writers.useFileWriter("auth.go", settings.getModuleName(), writer -> { ProtocolGenerator.GenerationContext context = contextBuilder.writer(writer).build(); protocolGenerator.generateAuth(context); }); writers.useFileWriter("endpoints_test.go", settings.getModuleName(), writer -> { ProtocolGenerator.GenerationContext context = contextBuilder.writer(writer).build(); protocolGenerator.generateEndpointResolutionTests(context); }); LOGGER.info("Generating protocol " + protocolGenerator.getProtocol() + " unit tests for " + service.getId()); writers.useFileWriter("protocol_test.go", settings.getModuleName(), writer -> { protocolGenerator.generateProtocolTests(contextBuilder.writer(writer).build()); }); protocolDocumentGenerator.generateInternalDocumentTypes(protocolGenerator, contextBuilder.build()); } LOGGER.fine("Flushing go writers"); List dependencies = writers.getDependencies(); writers.flushWriters(); GoModuleInfo goModuleInfo = new GoModuleInfo.Builder() .goDirective(settings.getGoDirective()) .dependencies(dependencies) .build(); GoModGenerator.writeGoMod(settings, fileManifest, goModuleInfo); LOGGER.fine("Generating build manifest file"); ManifestWriter.writeManifest(settings, model, fileManifest, goModuleInfo); } @Override protected Void getDefault(Shape shape) { return null; } @Override public Void structureShape(StructureShape shape) { if (shape.getId().getNamespace().equals(CodegenUtils.getSyntheticTypeNamespace())) { return null; } Symbol symbol = symbolProvider.toSymbol(shape); writers.useShapeWriter(shape, writer -> new StructureGenerator( model, symbolProvider, writer, service, shape, symbol, protocolGenerator).run()); return null; } @Override public Void stringShape(StringShape shape) { if (shape.hasTrait(EnumTrait.class)) { writers.useShapeWriter(shape, writer -> new EnumGenerator(symbolProvider, writer, shape).run()); } return null; } @Override public Void unionShape(UnionShape shape) { UnionGenerator generator = new UnionGenerator(model, symbolProvider, shape); writers.useShapeWriter(shape, generator::generateUnion); writers.useShapeExportedTestWriter(shape, generator::generateUnionExamples); return null; } @Override public Void serviceShape(ServiceShape shape) { if (!Objects.equals(service, shape)) { LOGGER.fine(() -> "Skipping `" + shape.getId() + "` because it is not `" + service.getId() + "`"); return null; } var protocol = protocolGenerator != null ? protocolGenerator.getApplicationProtocol() : ApplicationProtocol.createDefaultHttpApplicationProtocol(); var context = ProtocolGenerator.GenerationContext.builder() .protocolName(protocol.getName()) .integrations(integrations) .model(model) .service(service) .settings(settings) .symbolProvider(symbolProvider) .delegator(writers) .build(); // Write API client's package doc for the service. writers.useFileWriter("doc.go", settings.getModuleName(), (writer) -> { writer.writePackageDocs(String.format( "Package %s provides the API client, operations, and parameter types for %s.", CodegenUtils.getDefaultPackageImportName(settings.getModuleName()), CodegenUtils.getServiceTitle(shape, "the API"))); writer.writePackageDocs(""); writer.writePackageShapeDocs(shape); }); // Write API client type and utilities. writers.useShapeWriter(shape, serviceWriter -> { new ServiceGenerator(settings, model, symbolProvider, serviceWriter, shape, integrations, runtimePlugins, applicationProtocol).run(); // Generate each operation for the service. We do this here instead of via the // operation visitor method to // limit it to the operations bound to the service. TopDownIndex topDownIndex = model.getKnowledge(TopDownIndex.class); Set containedOperations = new TreeSet<>(topDownIndex.getContainedOperations(service)); for (OperationShape operation : containedOperations) { writers.useShapeWriter(operation, operationWriter -> new OperationGenerator(ctx, operationWriter, operation, protocolGenerator, runtimePlugins) .run()); } }); var clientOptions = new ClientOptions(context, protocol); writers.useFileWriter("options.go", settings.getModuleName(), clientOptions); return null; } @Override public Void intEnumShape(IntEnumShape shape) { writers.useShapeWriter(shape, writer -> new IntEnumGenerator(symbolProvider, writer, shape).run()); return null; } } DocumentationConverter.java000066400000000000000000000363451463735525100370570ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.commonmark.node.BlockQuote; import org.commonmark.node.FencedCodeBlock; import org.commonmark.node.Heading; import org.commonmark.node.HtmlBlock; import org.commonmark.node.ListBlock; import org.commonmark.node.ThematicBreak; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.jsoup.Jsoup; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; import org.jsoup.nodes.TextNode; import org.jsoup.safety.Safelist; import org.jsoup.select.NodeTraversor; import org.jsoup.select.NodeVisitor; import software.amazon.smithy.utils.CodeWriter; import software.amazon.smithy.utils.SetUtils; import software.amazon.smithy.utils.StringUtils; /** * Converts commonmark-formatted documentation into godoc format. */ public final class DocumentationConverter { // godoc only supports text blocks, root-level non-inline code blocks, headers, and links. // This allowlist strips out anything we can't reasonably convert, vastly simplifying the // node tree we end up having to crawl through. private static final Safelist GODOC_ALLOWLIST = new Safelist() .addTags("code", "pre", "ul", "ol", "li", "a", "br", "h1", "h2", "h3", "h4", "h5", "h6", "p") .addAttributes("a", "href") .addProtocols("a", "href", "http", "https"); // Construct a markdown parser that specifically ignores parsing indented code blocks. This // is because HTML blocks can have really wonky formatting that can be mis-attributed to an // indented code blocks. We may need to add a configuration option to re-enable this. private static final Parser MARKDOWN_PARSER = Parser.builder() .enabledBlockTypes(SetUtils.of( Heading.class, HtmlBlock.class, ThematicBreak.class, FencedCodeBlock.class, BlockQuote.class, ListBlock.class)) .build(); private DocumentationConverter() {} /** * Converts a commonmark formatted string into a godoc formatted string. * * @param docs commonmark formatted documentation * @return godoc formatted documentation */ public static String convert(String docs, int docWrapLength) { // Smithy's documentation format is commonmark, which can inline html. So here we convert // to html so we have a single known format to work with. String htmlDocs = HtmlRenderer.builder().escapeHtml(false).build().render(MARKDOWN_PARSER.parse(docs)); // Strip out tags and attributes we can't reasonably convert to godoc. htmlDocs = Jsoup.clean(htmlDocs, GODOC_ALLOWLIST); // Now we parse the html and visit the resultant nodes to render the godoc. FormattingVisitor formatter = new FormattingVisitor(docWrapLength); Node body = Jsoup.parse(htmlDocs).body(); NodeTraversor.traverse(formatter, body); return formatter.toString(); } private static class FormattingVisitor implements NodeVisitor { private static final Set TEXT_BLOCK_NODES = SetUtils.of( "br", "p", "h1", "h2", "h3", "h4", "h5", "h6", "note" ); private static final Set LIST_BLOCK_NODES = SetUtils.of("ul", "ol"); private static final Set CODE_BLOCK_NODES = SetUtils.of("pre", "code"); private final CodeWriter writer; // Godoc links are added at the end as `[text]: http://example.com` so keeping a collection to add them later private final Map links; private boolean needsListPrefix = false; private boolean shouldStripPrefixWhitespace = false; private int docWrapLength; private int listDepth; // the last line string written, used to calculate the remaining spaces to reach docWrapLength // and determine if a split char is needed between it and next string private String lastLineString; FormattingVisitor(int docWrapLength) { writer = new CodeWriter(); writer.trimTrailingSpaces(false); writer.trimBlankLines(); writer.insertTrailingNewline(false); this.docWrapLength = docWrapLength; this.links = new HashMap<>(); this.lastLineString = ""; } @Override public void head(Node node, int depth) { String name = node.nodeName(); if (isTopLevelCodeBlock(node, depth)) { writer.indent(); } if (node.nodeName().equals("a")) { // Logic to format anchors as Go Links https://tip.golang.org/doc/comment#links Element element = (Element) node; String text = element.text(); String url = element.absUrl("href"); if (url.isEmpty()) { // an empty anchor won't have a reference at the end, so just // output it directly to the output writer.writeInlineWithNoFormatting(text); return; } String wrappedAnchorText = "[" + text + "]"; writer.writeInlineWithNoFormatting(wrappedAnchorText); links.put(text, url); } Node parentNode = node.parentNode(); if (parentNode != null && parentNode.nodeName().equals("a") && node instanceof TextNode) { // anchor tags get processed twice: once as anchor tags and another one as // textNodes. Since this was already processed as anchor, no need to do anything else return; } if (node instanceof TextNode) { writeText((TextNode) node); } else if (isTopLevelCodeBlock(node, depth)) { writeNewline(); writeIndent(); } else if (LIST_BLOCK_NODES.contains(name)) { listDepth++; } else if (name.equals("li")) { // We don't actually write out the list prefix here in case the list element // starts with one or more text blocks. By deferring writing those out until // the first bit of actual text, we can ensure that no intermediary newlines // are kept. It also has the added benefit of eliminating empty list elements. needsListPrefix = true; } } private void writeText(TextNode node) { if (node.isBlank()) { return; } // Docs can have valid $ characters that shouldn't run through formatters. String text = node.text().replace("$", "$$"); if (shouldStripPrefixWhitespace) { shouldStripPrefixWhitespace = false; text = StringUtils.stripStart(text, " \t"); } if (listDepth > 0) { text = StringUtils.stripStart(text, " \t"); if (needsListPrefix) { needsListPrefix = false; text = " - " + text; writeNewline(); } writeWrappedText(text, "\n "); } else { writeWrappedText(text, "\n"); } } private void writeWrappedText(String text, String newLineIndent) { // check the last line's remaining space to see if test should be // split to 2 parts to write to current and next line // note that wrapped text will not contain desired indent at the beginning, // so indent will be added to the wrapped text when it is written to a new line // right boundary index of text to be written to the same line exceeding // neither docWrapLength nor text length int trailingLineCutoff = Math.min(Math.max(docWrapLength - lastLineString.length(), 0), text.length()); // the index of last space on the left of boundary if exist int lastSpace = text.substring(0, trailingLineCutoff).lastIndexOf(" "); // if current line is large enough to put the text, just append complete text to current line // otherwise, cut out next line string starting from lastSpace index String appendString = trailingLineCutoff < text.length() ? text.substring(0, lastSpace + 1) : text; String nextLineString = trailingLineCutoff < text.length() ? text.substring(lastSpace + 1) : ""; if (!appendString.isEmpty()) { ensureSplit(" ", appendString); writeInline(appendString); } if (!nextLineString.isEmpty()) { nextLineString = StringUtils.stripStart(newLineIndent, "\n") + StringUtils.wrap(nextLineString, docWrapLength, newLineIndent, false); writeNewline(); writeInline(nextLineString); } } private void ensureSplit(String split, String text) { if (!text.startsWith(split) && !lastLineString.isEmpty() && !lastLineString.endsWith(split)) { writeInline(split); } } private void writeNewline() { // While jsoup will strip out redundant whitespace, it will still leave some. If we // start a new line then we want to make sure we don't keep any prefixing whitespace. // need to refresh last line string shouldStripPrefixWhitespace = true; writer.write(""); lastLineString = ""; } private void writeInline(String contents, String... args) { // write text at the current line, update last line string String formatText = writer.format(contents, args); writer.writeInlineWithNoFormatting(formatText); formatText = lastLineString + formatText; lastLineString = formatText.substring(formatText.lastIndexOf("\n") + 1); } void writeIndent() { writer.setNewline("").write("").setNewline("\n"); } private boolean isTopLevelCodeBlock(Node node, int depth) { // The node must be a code block node if (!CODE_BLOCK_NODES.contains(node.nodeName())) { return false; } // It must either have no siblings or its siblings must be separate blocks. if (!allSiblingsAreBlocks(node)) { return false; } // Depth 0 will always be a "body" element, so depth 1 means it's top level. if (depth == 1) { return true; } // If its depth is 2, it could still be effectively top level if its parent is a p // node whose siblings are all blocks. Node parent = node.parent(); return depth == 2 && parent.nodeName().equals("p") && allSiblingsAreBlocks(parent); } /** * Determines whether a given node's siblings are all text blocks, code blocks, or lists. * *

Siblings that are blank text nodes are skipped. * * @param node The node whose siblings should be checked. * @return true if the node's siblings are blocks, otherwise false. */ private boolean allSiblingsAreBlocks(Node node) { // Find the nearest sibling to the left which is not a blank text node. Node previous = node.previousSibling(); while (true) { if (previous instanceof TextNode) { if (((TextNode) previous).isBlank()) { previous = previous.previousSibling(); continue; } } break; } // Find the nearest sibling to the right which is not a blank text node. Node next = node.nextSibling(); while (true) { if (next instanceof TextNode) { if (((TextNode) next).isBlank()) { next = next.nextSibling(); continue; } } break; } return (previous == null || isBlockNode(previous)) && (next == null || isBlockNode(next)); } private boolean isBlockNode(Node node) { String name = node.nodeName(); return TEXT_BLOCK_NODES.contains(name) || LIST_BLOCK_NODES.contains(name) || CODE_BLOCK_NODES.contains(name); } @Override public void tail(Node node, int depth) { String name = node.nodeName(); if (isTopLevelCodeBlock(node, depth)) { writer.dedent(); } if (TEXT_BLOCK_NODES.contains(name) || isTopLevelCodeBlock(node, depth)) { // A single newline is just treated as a line break, but gets // rendered as the same block. // Two ensure the text gets treated as a separate text block writeNewline(); writeNewline(); } else if (LIST_BLOCK_NODES.contains(name)) { listDepth--; if (listDepth == 0) { writeNewline(); } } else if (name.equals("li")) { // Clear out the expectation of a list element if the element's body is empty. needsListPrefix = false; } } @Override public String toString() { String result = writer.toString(); if (StringUtils.isBlank(result)) { return ""; } // Strip trailing whitespace from every line. We can't use the codewriter for this due to // not knowing when a line will end, as we typically build them up over many elements. String[] lines = result.split("\n", -1); for (int i = 0; i < lines.length; i++) { lines[i] = StringUtils.stripEnd(lines[i], " \t"); } result = String.join("\n", lines); // iterate over every link found and append it at the end String allLinks = links.entrySet().stream() .map(link -> formatLink(link)) .collect(Collectors.joining("\n")); if (!allLinks.isEmpty()) { result += "\n\n" + allLinks; } // Strip out leading and trailing newlines. return StringUtils.strip(result, "\n"); } private String formatLink(Map.Entry link) { String anchor = link.getKey(); String destination = link.getValue(); return "[%s]: %s".formatted(anchor, destination); } } } EnumGenerator.java000066400000000000000000000110741463735525100351210ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Set; import java.util.logging.Logger; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.model.shapes.StringShape; import software.amazon.smithy.model.traits.EnumDefinition; import software.amazon.smithy.model.traits.EnumTrait; import software.amazon.smithy.utils.SmithyInternalApi; import software.amazon.smithy.utils.StringUtils; /** * Renders enums and their constants. */ @SmithyInternalApi public final class EnumGenerator implements Runnable { private static final Logger LOGGER = Logger.getLogger(EnumGenerator.class.getName()); private final SymbolProvider symbolProvider; private final GoWriter writer; private final StringShape shape; public EnumGenerator(SymbolProvider symbolProvider, GoWriter writer, StringShape shape) { this.symbolProvider = symbolProvider; this.writer = writer; this.shape = shape; } @Override public void run() { Symbol symbol = symbolProvider.toSymbol(shape); EnumTrait enumTrait = shape.expectTrait(EnumTrait.class); writer.write("type $L string", symbol.getName()).write(""); // Don't generate constants if there are no explicitly modeled names. We only need to // look at one, since Smithy validates that if one has a name then they must all have // a name. if (enumTrait.getValues().get(0).getName().isPresent()) { writer.writeDocs(String.format("Enum values for %s", symbol.getName())); Set constants = new LinkedHashSet<>(); writer.openBlock("const (", ")", () -> { for (EnumDefinition definition : enumTrait.getValues()) { StringBuilder labelBuilder = new StringBuilder(symbol.getName()); String name = definition.getName().get(); for (String part : name.split("(?U)[\\W_]")) { if (part.matches(".*[a-z].*") && part.matches(".*[A-Z].*")) { // Mixed case names should not be changed other than first letter capitalized. labelBuilder.append(StringUtils.capitalize(part)); } else { // For all non-mixed case parts title case first letter, followed by all other lower cased. labelBuilder.append(StringUtils.capitalize(part.toLowerCase(Locale.US))); } } String label = labelBuilder.toString(); // If camel-casing would cause a conflict, don't camel-case this enum value. if (constants.contains(label)) { LOGGER.warning(String.format( "Multiple enums resolved to the same name, `%s`, using unaltered value for: %s", label, name)); label = name; } constants.add(label); definition.getDocumentation().ifPresent(writer::writeDocs); writer.write("$L $L = $S", label, symbol.getName(), definition.getValue()); } }).write(""); } writer.writeDocs(String.format("Values returns all known values for %s. Note that this can be expanded in the " + "future, and so it is only as up to date as the client.%n%nThe ordering of this slice is not " + "guaranteed to be stable across updates.", symbol.getName())); writer.openBlock("func ($L) Values() []$L {", "}", symbol.getName(), symbol.getName(), () -> { writer.openBlock("return []$L{", "}", symbol.getName(), () -> { for (EnumDefinition definition : enumTrait.getValues()) { writer.write("$S,", definition.getValue()); } }); }); } } EventStreamGenerator.java000066400000000000000000000465631463735525100364650ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.Set; import java.util.TreeSet; import java.util.function.Consumer; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.EventStreamIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.OperationShape; 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.ToShapeId; import software.amazon.smithy.utils.StringUtils; public final class EventStreamGenerator { public static final String AMZ_CONTENT_TYPE = "application/vnd.amazon.eventstream"; private static final String EVENT_STREAM_FILE = "eventstream.go"; private final GoSettings settings; private final Model model; private final GoDelegator writers; private final ServiceShape serviceShape; private final EventStreamIndex streamIndex; private final SymbolProvider symbolProvider; public EventStreamGenerator( GoSettings settings, Model model, GoDelegator writers, SymbolProvider symbolProvider, ServiceShape serviceShape ) { this.settings = settings; this.model = model; this.writers = writers; this.symbolProvider = symbolProvider; this.serviceShape = serviceShape; this.streamIndex = EventStreamIndex.of(this.model); } public void generateEventStreamInterfaces() { if (!hasEventStreamOperations()) { return; } final Set inputEvents = new TreeSet<>(); final Set outputEvents = new TreeSet<>(); TopDownIndex.of(model).getContainedOperations(serviceShape).forEach(operationShape -> { streamIndex.getInputInfo(operationShape).ifPresent(eventStreamInfo -> inputEvents.add(eventStreamInfo.getEventStreamMember().getTarget())); streamIndex.getOutputInfo(operationShape).ifPresent(eventStreamInfo -> outputEvents.add(eventStreamInfo.getEventStreamMember().getTarget())); }); Symbol context = SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT).build(); writers.useFileWriter(EVENT_STREAM_FILE, settings.getModuleName(), writer -> { inputEvents.forEach(shapeId -> { Shape shape = model.expectShape(shapeId); String writerInterfaceName = getEventStreamWriterInterfaceName(serviceShape, shape); writer.writeDocs(String.format("%s provides the interface for writing events to a stream.", writerInterfaceName)) .writeDocs("") .writeDocs("The writer's Close method must allow multiple concurrent calls."); writer.openBlock("type $L interface {", "}", writerInterfaceName, () -> { writer.write("Send($T, $T) error", context, symbolProvider.toSymbol(shape)); writer.write("Close() error"); writer.write("Err() error"); }); }); outputEvents.forEach(shapeId -> { Shape shape = model.expectShape(shapeId); String readerInterfaceName = getEventStreamReaderInterfaceName(serviceShape, shape); writer.writeDocs(String.format("%s provides the interface for reading events from a stream.", readerInterfaceName)) .writeDocs("") .writeDocs("The writer's Close method must allow multiple concurrent calls."); writer.openBlock("type $L interface {", "}", readerInterfaceName, () -> { writer.write("Events() <-chan $T", symbolProvider.toSymbol(shape)); writer.write("Close() error"); writer.write("Err() error"); }); }); }); } public boolean hasEventStreamOperations() { return hasEventStreamOperations(model, serviceShape, streamIndex); } public static boolean hasEventStreamOperations(Model model, ServiceShape serviceShape) { EventStreamIndex index = EventStreamIndex.of(model); return hasEventStreamOperations(model, serviceShape, index); } private static boolean hasEventStreamOperations(Model model, ServiceShape serviceShape, EventStreamIndex index) { return TopDownIndex.of(model).getContainedOperations(serviceShape).stream() .anyMatch(operationShape -> hasEventStream(model, operationShape, index)); } public void writeEventStreamImplementation(Consumer goWriterConsumer) { writers.useFileWriter(EVENT_STREAM_FILE, settings.getModuleName(), goWriterConsumer); } public boolean hasEventStream(OperationShape operationShape) { EventStreamIndex index = EventStreamIndex.of(model); return hasEventStreamOperations(model, serviceShape, index); } public static boolean hasEventStream(Model model, OperationShape operationShape) { EventStreamIndex index = EventStreamIndex.of(model); return hasEventStream(model, operationShape, index); } private static boolean hasEventStream(Model model, OperationShape operationShape, EventStreamIndex index) { return index.getInputInfo(operationShape).isPresent() || index.getOutputInfo(operationShape).isPresent(); } public void generateOperationEventStreamStructure(OperationShape operationShape) { if (!hasEventStream(model, operationShape)) { return; } writers.useShapeWriter(operationShape, writer -> generateOperationEventStreamStructure(writer, operationShape)); } private void generateOperationEventStreamStructure(GoWriter writer, OperationShape operationShape) { var opEventStreamStructure = getEventStreamOperationStructureSymbol(serviceShape, operationShape); var constructor = getEventStreamOperationStructureConstructor(serviceShape, operationShape); var inputInfo = streamIndex.getInputInfo(operationShape); var outputInfo = streamIndex.getOutputInfo(operationShape); writer.write(""" // $T provides the event stream handling for the $L operation. // // For testing and mocking the event stream this type should be initialized via // the $T constructor function. Using the functional options // to pass in nested mock behavior.""", opEventStreamStructure, operationShape.getId().getName(), constructor ); writer.openBlock("type $T struct {", "}", opEventStreamStructure, () -> { inputInfo.ifPresent(eventStreamInfo -> { var eventStreamTarget = eventStreamInfo.getEventStreamTarget(); var writerInterfaceName = getEventStreamWriterInterfaceName(serviceShape, eventStreamTarget); writer.writeDocs(String.format(""" %s is the EventStream writer for the %s events. This value is automatically set by the SDK when the API call is made Use this member when unit testing your code with the SDK to mock out the EventStream Writer.""", writerInterfaceName, eventStreamTarget.getId().getName(serviceShape))) .writeDocs("") .writeDocs("Must not be nil.") .write("Writer $L", writerInterfaceName).write(""); }); outputInfo.ifPresent(eventStreamInfo -> { var eventStreamTarget = eventStreamInfo.getEventStreamTarget(); var readerInterfaceName = getEventStreamReaderInterfaceName(serviceShape, eventStreamTarget); writer.writeDocs(String.format(""" %s is the EventStream reader for the %s events. This value is automatically set by the SDK when the API call is made Use this member when unit testing your code with the SDK to mock out the EventStream Reader.""", readerInterfaceName, eventStreamTarget.getId().getName(serviceShape))) .writeDocs("") .writeDocs("Must not be nil.") .write("Reader $L", readerInterfaceName).write(""); }); writer.write("done chan struct{}") .write("closeOnce $T", SymbolUtils.createValueSymbolBuilder("Once", SmithyGoDependency.SYNC) .build()) .write("err $P", SymbolUtils.createPointableSymbolBuilder("OnceErr", SmithyGoDependency.SMITHY_SYNC).build()); }).write(""); writer.write(""" // $T initializes an $T. // This function should only be used for testing and mocking the $T // stream within your application.""", constructor, opEventStreamStructure, opEventStreamStructure); if (inputInfo.isPresent()) { writer.writeDocs(""); writer.writeDocs("The Writer member must be set before writing events to the stream."); } if (outputInfo.isPresent()) { writer.writeDocs(""); writer.writeDocs("The Reader member must be set before reading events from the stream."); } writer.openBlock("func $T(optFns ...func($P)) $P {", "}", constructor, opEventStreamStructure, opEventStreamStructure, () -> writer .openBlock("es := &$L{", "}", opEventStreamStructure, () -> writer .write("done: make(chan struct{}),") .write("err: $T(),", SymbolUtils.createValueSymbolBuilder("NewOnceErr", SmithyGoDependency.SMITHY_SYNC).build())) .openBlock("for _, fn := range optFns {", "}", () -> writer .write("fn(es)")) .write("return es")).write(""); if (inputInfo.isPresent()) { writer.write(""" // Send writes the event to the stream blocking until the event is written. // Returns an error if the event was not written. func (es $P) Send(ctx $P, event $P) error { return es.Writer.Send(ctx, event) } """, opEventStreamStructure, SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT).build(), symbolProvider.toSymbol(inputInfo.get().getEventStreamTarget())); } if (outputInfo.isPresent()) { writer.write(""" // Events returns a channel to read events from. func (es $P) Events() <-chan $P { return es.Reader.Events() } """, opEventStreamStructure, symbolProvider.toSymbol(outputInfo.get().getEventStreamTarget())); } writer.write(""" // Close closes the stream. This will also cause the stream to be closed. // Close must be called when done using the stream API. Not calling Close // may result in resource leaks. // // Will close the underlying EventStream writer and reader, and no more events can be // sent or received. func (es $P) Close() error { es.closeOnce.Do(es.safeClose) return es.Err() } """, opEventStreamStructure); writer.openBlock("func (es $P) safeClose() {", "}", opEventStreamStructure, () -> { writer.write(""" close(es.done) """); if (inputInfo.isPresent()) { var newTicker = SymbolUtils.createValueSymbolBuilder("NewTicker", SmithyGoDependency.TIME).build(); var second = SymbolUtils.createValueSymbolBuilder("Second", SmithyGoDependency.TIME).build(); writer.write(""" t := $T($T) defer t.Stop() writeCloseDone := make(chan error) go func() { if err := es.Writer.Close(); err != nil { es.err.SetError(err) } close(writeCloseDone) }() select { case <-t.C: case <-writeCloseDone: } """, newTicker, second); } if (outputInfo.isPresent()) { writer.write("es.Reader.Close()"); } }).write(""); writer.writeDocs(""" Err returns any error that occurred while reading or writing EventStream Events from the service API's response. Returns nil if there were no errors."""); writer.openBlock("func (es $P) Err() error {", "}", opEventStreamStructure, () -> { writer.write(""" if err := es.err.Err(); err != nil { return err } """); if (inputInfo.isPresent()) { writer.write(""" if err := es.Writer.Err(); err != nil { return err } """); } if (outputInfo.isPresent()) { writer.write(""" if err := es.Reader.Err(); err != nil { return err } """); } writer.write("return nil"); }).write(""); writer.openBlock("func (es $P) waitStreamClose() {", "}", opEventStreamStructure, () -> { writer.write(""" type errorSet interface { ErrorSet() <-chan struct{} } """); if (inputInfo.isPresent()) { writer.write(""" var inputErrCh <-chan struct{} if v, ok := es.Writer.(errorSet); ok { inputErrCh = v.ErrorSet() } """); } if (outputInfo.isPresent()) { writer.write(""" var outputErrCh <-chan struct{} if v, ok := es.Reader.(errorSet); ok { outputErrCh = v.ErrorSet() } var outputClosedCh <-chan struct{} if v, ok := es.Reader.(interface{ Closed() <-chan struct{} }); ok { outputClosedCh = v.Closed() } """); } writer.openBlock("select {", "}", () -> { writer.write("case <-es.done:"); if (inputInfo.isPresent()) { writer.write(""" case <-inputErrCh: es.err.SetError(es.Writer.Err()) es.Close() """); } if (outputInfo.isPresent()) { writer.write(""" case <-outputErrCh: es.err.SetError(es.Reader.Err()) es.Close() case <-outputClosedCh: if err := es.Reader.Err(); err != nil { es.err.SetError(es.Reader.Err()) } es.Close() """); } }); }).write(""); } public static Symbol getEventStreamOperationStructureConstructor( ServiceShape serviceShape, OperationShape operationShape ) { var symbol = getEventStreamOperationStructureSymbol(serviceShape, operationShape); return SymbolUtils.createValueSymbolBuilder("New" + symbol.getName()).build(); } public static Symbol getEventStreamOperationStructureSymbol( ServiceShape serviceShape, OperationShape operationShape ) { String name = StringUtils.capitalize(operationShape.getId().getName(serviceShape)); return SymbolUtils.createPointableSymbolBuilder(name + "EventStream") .build(); } public static String getEventStreamWriterInterfaceName(ServiceShape serviceShape, ToShapeId shape) { String name = StringUtils.capitalize(shape.toShapeId().getName(serviceShape)); return name + "Writer"; } public static String getEventStreamReaderInterfaceName(ServiceShape serviceShape, ToShapeId shape) { String name = StringUtils.capitalize(shape.toShapeId().getName(serviceShape)); return name + "Reader"; } } GoCodegenContext.java000066400000000000000000000025421463735525100355450ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.List; import software.amazon.smithy.build.FileManifest; import software.amazon.smithy.codegen.core.CodegenContext; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.codegen.core.WriterDelegator; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.model.Model; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public record GoCodegenContext( Model model, GoSettings settings, SymbolProvider symbolProvider, FileManifest fileManifest, WriterDelegator writerDelegator, List integrations ) implements CodegenContext {} GoCodegenPlugin.java000066400000000000000000000043451463735525100353620ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.logging.Logger; import software.amazon.smithy.build.PluginContext; import software.amazon.smithy.build.SmithyBuildPlugin; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.model.Model; /** * Plugin to trigger Go code generation. */ public final class GoCodegenPlugin implements SmithyBuildPlugin { private static final Logger LOGGER = Logger.getLogger(GoCodegenPlugin.class.getName()); @Override public String getName() { return "go-codegen"; } @Override public void execute(PluginContext context) { String onlyBuild = System.getenv("SMITHY_GO_BUILD_API"); if (onlyBuild != null && !onlyBuild.isEmpty()) { String targetServiceId = GoSettings.from(context.getSettings()).getService().toString(); boolean found = false; for (String includeServiceId : onlyBuild.split(",")) { if (targetServiceId.startsWith(includeServiceId)) { found = true; break; } } if (!found) { LOGGER.info("skipping " + targetServiceId); return; } } new CodegenVisitor(context).execute(); } /** * Creates a Go symbol provider. * * @param model The model to generate symbols for. * @param settings The Gosettings to use to create symbol provider * @return Returns the created provider. */ public static SymbolProvider createSymbolProvider(Model model, GoSettings settings) { return new SymbolVisitor(model, settings); } } GoDelegator.java000066400000000000000000000054451463735525100345470ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.function.Consumer; import software.amazon.smithy.build.FileManifest; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.codegen.core.WriterDelegator; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.utils.SmithyInternalApi; /** * Manages writers for Go files.Based off of GoWriterDelegator adding support * for getting shape specific GoWriters. */ @SmithyInternalApi public final class GoDelegator extends WriterDelegator { private final SymbolProvider symbolProvider; public GoDelegator(FileManifest fileManifest, SymbolProvider symbolProvider) { super(fileManifest, symbolProvider, (filename, namespace) -> new GoWriter(namespace)); this.symbolProvider = symbolProvider; } /** * Gets a previously created writer or creates a new one for the Go test file for the associated shape. * * @param shape Shape to create the writer for. * @param writerConsumer Consumer that accepts and works with the file. */ public void useShapeTestWriter(Shape shape, Consumer writerConsumer) { var symbol = symbolProvider.toSymbol(shape); var filename = symbol.getDefinitionFile(); var testFilename = new StringBuilder(filename) .insert(filename.lastIndexOf(".go"), "_test") .toString(); useFileWriter(testFilename, symbol.getNamespace(), writerConsumer); } /** * Gets a previously created writer or creates a new one for the Go public package test file for the associated * shape. * * @param shape Shape to create the writer for. * @param writerConsumer Consumer that accepts and works with the file. */ public void useShapeExportedTestWriter(Shape shape, Consumer writerConsumer) { var symbol = symbolProvider.toSymbol(shape); var filename = symbol.getDefinitionFile(); var testFilename = new StringBuilder(filename) .insert(filename.lastIndexOf(".go"), "_exported_test") .toString(); useFileWriter(testFilename, symbol.getNamespace() + "_test", writerConsumer); } } GoDependency.java000066400000000000000000000335541463735525100347210ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.TreeSet; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolDependency; import software.amazon.smithy.codegen.core.SymbolDependencyContainer; import software.amazon.smithy.utils.SetUtils; import software.amazon.smithy.utils.SmithyBuilder; /** * */ public final class GoDependency implements SymbolDependencyContainer, Comparable { private final Type type; private final String sourcePath; private final String importPath; private final String alias; private final String version; private final Set dependencies; private final SymbolDependency symbolDependency; private GoDependency(Builder builder) { this.type = SmithyBuilder.requiredState("type", builder.type); this.sourcePath = !this.type.equals(Type.STANDARD_LIBRARY) ? SmithyBuilder.requiredState("sourcePath", builder.sourcePath) : ""; this.importPath = SmithyBuilder.requiredState("importPath", builder.importPath); this.alias = builder.alias; this.version = SmithyBuilder.requiredState("version", builder.version); this.dependencies = builder.dependencies; this.symbolDependency = SymbolDependency.builder() .dependencyType(this.type.toString()) .packageName(this.sourcePath) .version(this.version) .build(); } /** * Given two {@link SymbolDependency} referring to the same package, return the minimum dependency version using * minimum version selection. The version strings must be semver compatible. * * @param dx the first dependency * @param dy the second dependency * @return the minimum dependency */ public static SymbolDependency mergeByMinimumVersionSelection(SymbolDependency dx, SymbolDependency dy) { SemanticVersion sx = SemanticVersion.parseVersion(dx.getVersion()); SemanticVersion sy = SemanticVersion.parseVersion(dy.getVersion()); // This *shouldn't* happen in Go since the Go module import path must end with the major version component. // Exception is the case where the major version is 0 or 1. if (sx.getMajor() != sy.getMajor() && !(sx.getMajor() == 0 || sy.getMajor() == 0)) { throw new CodegenException(String.format("Dependency %s has conflicting major versions", dx.getPackageName())); } int cmp = sx.compareTo(sy); if (cmp < 0) { return dy; } else if (cmp > 0) { return dx; } return dx; } /** * Get the the set of {@link GoDependency} required by this dependency. * * @return the set of dependencies */ public Set getGoDependencies() { return this.dependencies; } /** * Get the symbol dependency representing the dependency. * * @return the symbol dependency */ public SymbolDependency getSymbolDependency() { return symbolDependency; } /** * Get the type of dependency. * * @return the dependency type */ public Type getType() { return type; } /** * Get the source code path of the dependency. * * @return the dependency source code path */ public String getSourcePath() { return sourcePath; } /** * Get the import path of the dependency. * * @return the import path of the dependency */ public String getImportPath() { return importPath; } /** * Get the alias of the module to use. * * @return the alias */ public String getAlias() { return alias; } /** * Get the version of the dependency. * * @return the version */ public String getVersion() { return version; } /** * Creates a Symbol for a name exported by this package. * @param name The name. * @return The symbol. */ public Symbol valueSymbol(String name) { return SymbolUtils.createValueSymbolBuilder(name, this).build(); } /** * Creates a Symbol for a `const` exported by this package. * @param name The name. * @return The symbol. */ public Symbol constSymbol(String name) { return SymbolUtils.createValueSymbolBuilder(name, this).build(); } /** * Creates a Symbol for a `func` exported by this package. * @param name The name. * @return The symbol. */ public Symbol func(String name) { return SymbolUtils.createValueSymbolBuilder(name, this).build(); } /** * Creates a Symbol for a `struct` exported by this package. * @param name The name. * @return The symbol. */ public Symbol struct(String name) { return SymbolUtils.createPointableSymbolBuilder(name, this).build(); } /** * Creates a Symbol for a `Value` exported by this package. * @param name The name. * @return The symbol. */ public Symbol interfaceSymbol(String name) { return SymbolUtils.createValueSymbolBuilder(name, this).build(); } /** * Creates a pointable Symbol for a name exported by this package. * @param name The name. * @return The symbol. */ public Symbol pointableSymbol(String name) { return SymbolUtils.createPointableSymbolBuilder(name, this).build(); } @Override public List getDependencies() { Set symbolDependencySet = new TreeSet<>(SetUtils.of(getSymbolDependency())); symbolDependencySet.addAll(resolveDependencies(getGoDependencies(), new TreeSet<>(SetUtils.of(this)))); return new ArrayList<>(symbolDependencySet); } private Set resolveDependencies(Set goDependencies, Set processed) { Set symbolDependencies = new TreeSet<>(); if (goDependencies.size() == 0) { return symbolDependencies; } Set dependenciesToResolve = new TreeSet<>(); for (GoDependency dependency : goDependencies) { if (processed.contains(dependency)) { continue; } processed.add(dependency); symbolDependencies.add(dependency.getSymbolDependency()); dependenciesToResolve.addAll(dependency.getGoDependencies()); } symbolDependencies.addAll(resolveDependencies(dependenciesToResolve, processed)); return symbolDependencies; } public static Builder builder() { return new Builder(); } @Override public boolean equals(Object o) { if (this == o) { return true; } else if (!(o instanceof GoDependency)) { return false; } GoDependency other = (GoDependency) o; return this.type.equals(other.type) && this.sourcePath.equals(other.sourcePath) && this.importPath.equals(other.importPath) && this.alias.equals(other.alias) && this.version.equals(other.version) && this.dependencies.equals(other.dependencies); } @Override public int hashCode() { return Objects.hash(type, sourcePath, importPath, alias, version, dependencies); } @Override public int compareTo(GoDependency o) { if (equals(o)) { return 0; } int importPathCompare = importPath.compareTo(o.importPath); if (importPathCompare != 0) { return importPathCompare; } if (alias != null) { int aliasCompare = alias.compareTo(o.alias); if (aliasCompare != 0) { return aliasCompare; } } return version.compareTo(o.version); } /** * Represents a dependency type. */ public enum Type { STANDARD_LIBRARY, DEPENDENCY; @Override public String toString() { switch (this) { case STANDARD_LIBRARY: return "stdlib"; case DEPENDENCY: return "dependency"; default: return "unknown"; } } } /** * Get {@link GoDependency} representing the provided module description. * * @param sourcePath the root source path for the given code * @param importPath the import path of the package * @param version the version of source module * @param alias a default alias to use when importing the package, can be null * @return the dependency */ public static GoDependency moduleDependency(String sourcePath, String importPath, String version, String alias) { GoDependency.Builder builder = GoDependency.builder() .type(GoDependency.Type.DEPENDENCY) .sourcePath(sourcePath) .importPath(importPath) .version(version); if (alias != null) { builder.alias(alias); } return builder.build(); } /** * Get {@link GoDependency} representing the standard library import description. * * @param importPath the import path of the package * @param version the version of source module * @return the dependency */ public static GoDependency standardLibraryDependency(String importPath, String version) { return GoDependency.builder() .type(GoDependency.Type.STANDARD_LIBRARY) .importPath(importPath) .version(version) .build(); } /** * Get {@link GoDependency} representing the standard library import description. * * @param importPath the import path of the package * @param version the version of source module * @param alias the alias for stdlib dependency * @return the dependency */ public static GoDependency standardLibraryDependency(String importPath, String version, String alias) { GoDependency.Builder builder = GoDependency.builder() .type(GoDependency.Type.STANDARD_LIBRARY) .importPath(importPath) .version(version); if (alias != null) { builder.alias(alias); } return builder.build(); } /** * Builder for {@link GoDependency}. */ public static final class Builder implements SmithyBuilder { private Type type; private String sourcePath; private String importPath; private String alias; private String version; private final Set dependencies = new TreeSet<>(); private Builder() { } /** * Set the dependency type. * * @param type dependency type * @return the builder */ public Builder type(Type type) { this.type = type; return this; } /** * Set the source path. * * @param sourcePath the source path root * @return the builder */ public Builder sourcePath(String sourcePath) { this.sourcePath = sourcePath; return this; } /** * Set the import path. * * @param importPath the import path * @return the builder */ public Builder importPath(String importPath) { this.importPath = importPath; return this; } /** * Set the dependency alias. * * @param alias the alias * @return the builder */ public Builder alias(String alias) { this.alias = alias; return this; } /** * Set the dependency version. * * @param version the version * @return the builder */ public Builder version(String version) { this.version = version; return this; } /** * Set the collection of {@link GoDependency} required by this dependency. * * @param dependencies a collection of dependencies * @return the builder */ public Builder dependencies(Collection dependencies) { this.dependencies.clear(); this.dependencies.addAll(dependencies); return this; } /** * Add a dependency on another {@link GoDependency}. * * @param dependency the dependency * @return the builder */ public Builder addDependency(GoDependency dependency) { this.dependencies.add(dependency); return this; } /** * Remove a dependency on another {@link GoDependency}. * * @param dependency the dependency * @return the builder */ public Builder removeDependency(GoDependency dependency) { this.dependencies.remove(dependency); return this; } @Override public GoDependency build() { return new GoDependency(this); } } } GoEventStreamIndex.java000066400000000000000000000115651463735525100360660ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.EventStreamIndex; import software.amazon.smithy.model.knowledge.EventStreamInfo; import software.amazon.smithy.model.knowledge.KnowledgeIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.ToShapeId; /** * Provides a knowledge index about event streams and their corresponding usage in operations. */ public class GoEventStreamIndex implements KnowledgeIndex { final Map>> inputEventStreams = new HashMap<>(); final Map>> outputEventStreams = new HashMap<>(); public GoEventStreamIndex(Model model) { EventStreamIndex eventStreamIndex = EventStreamIndex.of(model); for (ServiceShape serviceShape : model.getServiceShapes()) { final Map> serviceInputStreams = new HashMap<>(); final Map> serviceOutputStreams = new HashMap<>(); TopDownIndex.of(model).getContainedOperations(serviceShape).forEach(operationShape -> { eventStreamIndex.getInputInfo(operationShape).ifPresent(eventStreamInfo -> { ShapeId eventStreamTargetId = eventStreamInfo.getEventStreamTarget().getId(); if (serviceInputStreams.containsKey(eventStreamTargetId)) { serviceInputStreams.get(eventStreamTargetId).add(eventStreamInfo); } else { TreeSet infos = new TreeSet<>( Comparator.comparing(EventStreamInfo::getOperation)); infos.add(eventStreamInfo); serviceInputStreams.put(eventStreamTargetId, infos); } }); eventStreamIndex.getOutputInfo(operationShape).ifPresent(eventStreamInfo -> { ShapeId eventStreamTargetId = eventStreamInfo.getEventStreamTarget().getId(); if (serviceOutputStreams.containsKey(eventStreamTargetId)) { serviceOutputStreams.get(eventStreamTargetId).add(eventStreamInfo); } else { TreeSet infos = new TreeSet<>( Comparator.comparing(EventStreamInfo::getOperation)); infos.add(eventStreamInfo); serviceOutputStreams.put(eventStreamTargetId, infos); } }); }); if (!serviceInputStreams.isEmpty()) { inputEventStreams.put(serviceShape.getId(), serviceInputStreams); } if (!serviceOutputStreams.isEmpty()) { outputEventStreams.put(serviceShape.getId(), serviceOutputStreams); } } } /** * Get the input event streams and their usages in operations for the provided service. * * @param service the service shape * @return the map of event stream unions to their corresponding event infos */ public Optional>> getInputEventStreams(ToShapeId service) { return Optional.ofNullable(inputEventStreams.get(service.toShapeId())); } /** * Get the output event streams and their usages in operations for the provided service. * * @param service the service shape * @return the map of event stream unions to their corresponding event infos */ public Optional>> getOutputEventStreams(ToShapeId service) { return Optional.ofNullable(outputEventStreams.get(service.toShapeId())); } /** * Returns a {@link GoEventStreamIndex} for the given model. * * @param model the model * @return the knowledge index */ public static GoEventStreamIndex of(Model model) { return model.getKnowledge(GoEventStreamIndex.class, GoEventStreamIndex::new); } } GoJmespathExpressionGenerator.java000066400000000000000000000133211463735525100403330ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import static software.amazon.smithy.go.codegen.util.ShapeUtil.STRING_SHAPE; import static software.amazon.smithy.go.codegen.util.ShapeUtil.expectMember; import static software.amazon.smithy.go.codegen.util.ShapeUtil.listOf; import static software.amazon.smithy.utils.StringUtils.capitalize; import java.util.List; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.jmespath.JmespathExpression; import software.amazon.smithy.jmespath.ast.FieldExpression; import software.amazon.smithy.jmespath.ast.FunctionExpression; import software.amazon.smithy.jmespath.ast.ProjectionExpression; import software.amazon.smithy.jmespath.ast.Subexpression; import software.amazon.smithy.model.shapes.ListShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.utils.SmithyInternalApi; /** * Traverses a JMESPath expression, producing a series of statements that evaluate the entire expression. The generator * is shape-aware and the return indicates the underlying shape being referenced in the final result. *
* Note that the use of writer.write() here is deliberate, it's easier to structure the code in that way instead of * trying to recursively compose/organize Writable templates. */ @SmithyInternalApi public class GoJmespathExpressionGenerator { private final GoCodegenContext ctx; private final GoWriter writer; private final Shape input; private final JmespathExpression root; private int idIndex = 1; public GoJmespathExpressionGenerator(GoCodegenContext ctx, GoWriter writer, Shape input, JmespathExpression expr) { this.ctx = ctx; this.writer = writer; this.input = input; this.root = expr; } public Result generate(String ident) { writer.write("v1 := $L", ident); return visit(root, input); } private Result visit(JmespathExpression expr, Shape current) { if (expr instanceof FunctionExpression tExpr) { return visitFunction(tExpr, current); } else if (expr instanceof FieldExpression tExpr) { return visitField(tExpr, current); } else if (expr instanceof Subexpression tExpr) { return visitSub(tExpr, current); } else if (expr instanceof ProjectionExpression tExpr) { return visitProjection(tExpr, current); } else { throw new CodegenException("unhandled jmespath expression " + expr.getClass().getSimpleName()); } } private Result visitProjection(ProjectionExpression expr, Shape current) { var left = visit(expr.getLeft(), current); // left of projection HAS to be an array by spec, otherwise something is wrong if (!left.shape.isListShape()) { throw new CodegenException("left side of projection did not create a list"); } var leftMember = expectMember(ctx.model(), (ListShape) left.shape); // We have to know the element type for the list that we're generating, use a dummy writer to "peek" ahead and // get the traversal result var lookahead = new GoJmespathExpressionGenerator(ctx, new GoWriter(""), leftMember, expr.getRight()) .generate("v"); ++idIndex; writer.write(""" var v$L []$P for _, v := range $L {""", idIndex, ctx.symbolProvider().toSymbol(lookahead.shape), left.ident); // new scope inside loop, but now we actually want to write the contents // projected.shape is the _member_ of the resulting list var projected = new GoJmespathExpressionGenerator(ctx, writer, leftMember, expr.getRight()) .generate("v"); writer.write("v$1L = append(v$1L, $2L)", idIndex, projected.ident); writer.write("}"); return new Result(listOf(projected.shape), "v" + idIndex); } private Result visitSub(Subexpression expr, Shape current) { var left = visit(expr.getLeft(), current); return visit(expr.getRight(), left.shape); } private Result visitField(FieldExpression expr, Shape current) { ++idIndex; writer.write("v$L := v$L.$L", idIndex, idIndex - 1, capitalize(expr.getName())); return new Result(expectMember(ctx.model(), current, expr.getName()), "v" + idIndex); } private Result visitFunction(FunctionExpression expr, Shape current) { return switch (expr.name) { case "keys" -> visitKeysFunction(expr.arguments, current); default -> throw new CodegenException("unsupported function " + expr.name); }; } private Result visitKeysFunction(List args, Shape current) { if (args.size() != 1) { throw new CodegenException("unexpected keys() arg length " + args.size()); } var arg = visit(args.get(0), current); ++idIndex; writer.write(""" var v$1L []string for k := range $2L { v$1L = append(v$1L, k) }""", idIndex, arg.ident); return new Result(listOf(STRING_SHAPE), "v" + idIndex); } public record Result(Shape shape, String ident) {} } GoModGenerator.java000066400000000000000000000053211463735525100352200ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; import java.util.logging.Logger; import software.amazon.smithy.build.FileManifest; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.utils.SmithyInternalApi; /** * Generates a go.mod file for the project. * *

See here for more information on the format: https://github.com/golang/go/wiki/Modules#gomod */ @SmithyInternalApi public final class GoModGenerator { private static final Logger LOGGER = Logger.getLogger(GoModGenerator.class.getName()); private GoModGenerator() {} public static void writeGoMod( GoSettings settings, FileManifest manifest, GoModuleInfo goModuleInfo ) { Boolean generateGoMod = settings.getGenerateGoMod(); if (!generateGoMod) { return; } Path goModFile = manifest.getBaseDir().resolve("go.mod"); LOGGER.fine("Generating go.mod file at path " + goModFile.toString()); // `go mod init` will fail if the `go.mod` already exists, so this deletes // it if it's present in the output. While it's technically possible // to simply edit the file, it's easier to just start fresh. if (Files.exists(goModFile)) { try { Files.delete(goModFile); } catch (IOException e) { throw new CodegenException("Failed to delete existing go.mod file", e); } } manifest.addFile(goModFile); CodegenUtils.runCommand("go mod init " + settings.getModuleName(), manifest.getBaseDir()); for (Map.Entry dependency : goModuleInfo.getMinimumNonStdLibDependencies().entrySet()) { CodegenUtils.runCommand( String.format("go mod edit -require=%s@%s", dependency.getKey(), dependency.getValue()), manifest.getBaseDir()); } CodegenUtils.runCommand( String.format("go mod edit -go=%s", goModuleInfo.getGoDirective()), manifest.getBaseDir()); } } GoModuleInfo.java000066400000000000000000000112021463735525100346660ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.function.Predicate; import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.SymbolDependency; import software.amazon.smithy.utils.BuilderRef; import software.amazon.smithy.utils.SmithyBuilder; public final class GoModuleInfo { public static final String DEFAULT_GO_DIRECTIVE = "1.15"; private List dependencies; private List stdLibDependencies; private List nonStdLibDependencies; private String goDirective; private Map minimumNonStdLibDependencies; private GoModuleInfo(Builder builder) { goDirective = SmithyBuilder.requiredState("goDirective", builder.goDirective); dependencies = builder.dependencies.copy(); // Intermediate dependency information stdLibDependencies = gatherStdLibDependencies(); nonStdLibDependencies = gatherNonStdLibDependencies(); // Module information used by ManifestWriter and GoModGenerator goDirective = calculateMinimumGoDirective(); minimumNonStdLibDependencies = gatherMinimumNonStdDependencies(); } public String getGoDirective() { return goDirective; } public Map getMinimumNonStdLibDependencies() { return minimumNonStdLibDependencies; } private String calculateMinimumGoDirective() { String minimumGoDirective = goDirective; if (minimumGoDirective.compareTo(DEFAULT_GO_DIRECTIVE) < 0) { throw new IllegalArgumentException( "`goDirective` must be greater than or equal to the default go directive (" + DEFAULT_GO_DIRECTIVE + "): " + minimumGoDirective); } for (SymbolDependency dependency : stdLibDependencies) { var otherVersion = dependency.getVersion(); if (minimumGoDirective.compareTo(otherVersion) < 0) { minimumGoDirective = otherVersion; } } return minimumGoDirective; } private List gatherStdLibDependencies() { return filterDependencies(dependency -> dependency.getDependencyType().equals(GoDependency.Type.STANDARD_LIBRARY.toString())); } private List gatherNonStdLibDependencies() { return filterDependencies(dependency -> !dependency.getDependencyType().equals(GoDependency.Type.STANDARD_LIBRARY.toString())); } private List filterDependencies( Predicate predicate ) { List filteredDependencies = new ArrayList<>(); for (SymbolDependency dependency : dependencies) { if (predicate.test(dependency)) { filteredDependencies.add(dependency); } } return filteredDependencies; } private Map gatherMinimumNonStdDependencies() { return SymbolDependency.gatherDependencies(nonStdLibDependencies.stream(), GoDependency::mergeByMinimumVersionSelection).entrySet().stream().flatMap( entry -> entry.getValue().entrySet().stream()).collect( Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().getVersion(), (a, b) -> b, TreeMap::new)); } public static class Builder implements SmithyBuilder { private String goDirective = DEFAULT_GO_DIRECTIVE; private final BuilderRef> dependencies = BuilderRef.forList(); public Builder goDirective(String goDirective) { this.goDirective = goDirective; return this; } public Builder dependencies(List dependencies) { this.dependencies.clear(); this.dependencies.get().addAll(dependencies); return this; } @Override public GoModuleInfo build() { return new GoModuleInfo(this); } } } GoSettings.java000066400000000000000000000241001463735525100344260ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.Arrays; import java.util.Objects; import java.util.Optional; import java.util.Set; 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.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.codegen.core.CodegenException; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.ServiceIndex; 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.protocol.traits.Rpcv2CborTrait; import software.amazon.smithy.utils.SmithyInternalApi; /** * Settings used by {@link GoCodegenPlugin}. */ @SmithyInternalApi public final class GoSettings { public static final Set PROTOCOLS_BY_PRIORITY = Set.of( Rpcv2CborTrait.ID, AwsJson1_0Trait.ID, AwsJson1_1Trait.ID, RestJson1Trait.ID, RestXmlTrait.ID, AwsQueryTrait.ID, Ec2QueryTrait.ID ); private static final String SERVICE = "service"; private static final String MODULE_NAME = "module"; private static final String MODULE_DESCRIPTION = "moduleDescription"; private static final String MODULE_VERSION = "moduleVersion"; private static final String GENERATE_GO_MOD = "generateGoMod"; private static final String GO_DIRECTIVE = "goDirective"; private ShapeId service; private String moduleName; private String moduleDescription = ""; private String moduleVersion; private Boolean generateGoMod = false; private String goDirective = GoModuleInfo.DEFAULT_GO_DIRECTIVE; private ShapeId protocol; private ArtifactType artifactType; @SmithyInternalApi public enum ArtifactType { CLIENT, SERVER; } /** * Create a settings object from a configuration object node. * * @param config Config object to load. * @return Returns the extracted settings. */ public static GoSettings from(ObjectNode config) { return from(config, ArtifactType.CLIENT); } @SmithyInternalApi public static GoSettings from(ObjectNode config, ArtifactType artifactType) { GoSettings settings = new GoSettings(); config.warnIfAdditionalProperties( Arrays.asList(SERVICE, MODULE_NAME, MODULE_DESCRIPTION, MODULE_VERSION, GENERATE_GO_MOD, GO_DIRECTIVE)); settings.setArtifactType(artifactType); settings.setService(config.expectStringMember(SERVICE).expectShapeId()); settings.setModuleName(config.expectStringMember(MODULE_NAME).getValue()); settings.setModuleDescription(config.getStringMemberOrDefault( MODULE_DESCRIPTION, settings.getModuleName() + " client")); settings.setModuleVersion(config.getStringMemberOrDefault(MODULE_VERSION, null)); settings.setGenerateGoMod(config.getBooleanMemberOrDefault(GENERATE_GO_MOD, false)); settings.setGoDirective(config.getStringMemberOrDefault(GO_DIRECTIVE, GoModuleInfo.DEFAULT_GO_DIRECTIVE)); return settings; } /** * Gets the id of the service that is being generated. * * @return Returns the service id. * @throws NullPointerException if the service has not been set. */ public ShapeId getService() { return Objects.requireNonNull(service, SERVICE + " not set"); } /** * Gets the corresponding {@link ServiceShape} from a model. * * @param model Model to search for the service shape by ID. * @return Returns the found {@code Service}. * @throws NullPointerException if the service has not been set. * @throws CodegenException if the service is invalid or not found. */ public ServiceShape getService(Model model) { return model .getShape(getService()) .orElseThrow(() -> new CodegenException("Service shape not found: " + getService())) .asServiceShape() .orElseThrow(() -> new CodegenException("Shape is not a Service: " + getService())); } /** * Sets the service to generate. * * @param service The service to generate. */ public void setService(ShapeId service) { this.service = Objects.requireNonNull(service); } /** * Gets the required module name for the module that will be generated. * * @return Returns the module name. * @throws NullPointerException if the module name has not been set. */ public String getModuleName() { return Objects.requireNonNull(moduleName, MODULE_NAME + " not set"); } /** * Sets the name of the module to generate. * * @param moduleName The name of the module to generate. */ public void setModuleName(String moduleName) { this.moduleName = Objects.requireNonNull(moduleName); } /** * Sets the name of the module to generate. * * @param moduleName The name of the module to generate. */ public void setModule(String moduleName) { this.moduleName = Objects.requireNonNull(moduleName); } /** * Gets the optional module description for the module that will be generated. * * @return Returns the module description. */ public String getModuleDescription() { return moduleDescription; } /** * Sets the description of the module to generate. * * @param moduleDescription The description of the module to generate. */ public void setModuleDescription(String moduleDescription) { this.moduleDescription = Objects.requireNonNull(moduleDescription); } /** * Gets the optional module version for the module that will be generated. * * @return Returns the module version. */ public Optional getModuleVersion() { return Optional.ofNullable(moduleVersion); } /** * Sets the version of the module to generate. * * @param moduleVersion The version of the module to generate. */ public void setModuleVersion(String moduleVersion) { if (moduleVersion != null) { this.moduleVersion = moduleVersion; } } /** * Gets the flag for generating go.mod file. * * @return Returns if go.mod will be generated (true) or not (false) */ public Boolean getGenerateGoMod() { return generateGoMod; } /** * Sets the flag for generating go.mod file. * * @param generateGoMod If go.mod will be generated (true) or not (false) */ public void setGenerateGoMod(Boolean generateGoMod) { this.generateGoMod = Objects.requireNonNull(generateGoMod); } /** * Gets the optional Go directive for the module that will be generated. * * @return Returns the Go directive. */ public String getGoDirective() { return goDirective; } /** * Sets the Go directive of the module to generate. * * @param goDirective The Go directive of the module to generate. */ public void setGoDirective(String goDirective) { this.goDirective = Objects.requireNonNull(goDirective); } /** * Gets the configured protocol to generate. * * @return Returns the configured protocol. */ public ShapeId getProtocol() { return protocol; } /** * Resolves the highest priority protocol from a service shape that is * supported by the generator. * * @param serviceIndex Service index containing the support * @param service Service to get the protocols from if "protocols" is not set. * @param supportedProtocolTraits The set of protocol traits supported by the generator. * @return Returns the resolved protocol name. * @throws UnresolvableProtocolException if no protocol could be resolved. */ public ShapeId resolveServiceProtocol( ServiceIndex serviceIndex, ServiceShape service, Set supportedProtocolTraits) { if (protocol != null) { return protocol; } Set resolvedProtocols = serviceIndex.getProtocols(service).keySet(); var byPriority = PROTOCOLS_BY_PRIORITY.stream() .filter(resolvedProtocols::contains) .toList(); return byPriority.stream() .filter(supportedProtocolTraits::contains) .findFirst() .orElseThrow(() -> new UnresolvableProtocolException(String.format( "The %s service supports the following unsupported protocols %s. The following protocol " + "generators were found on the class path: %s", service.getId(), resolvedProtocols, supportedProtocolTraits))); } /** * Sets the protocol to generate. * * @param protocol Protocols to generate. */ public void setProtocol(ShapeId protocol) { this.protocol = Objects.requireNonNull(protocol); } @SmithyInternalApi public ArtifactType getArtifactType() { return artifactType; } @SmithyInternalApi public void setArtifactType(ArtifactType artifactType) { this.artifactType = artifactType; } } GoStackStepMiddlewareGenerator.java000066400000000000000000000403121463735525100403770ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.function.BiConsumer; import software.amazon.smithy.codegen.core.Symbol; /** * Helper for generating stack step middleware. */ public final class GoStackStepMiddlewareGenerator { private static final Symbol CONTEXT_TYPE = SymbolUtils.createValueSymbolBuilder( "Context", SmithyGoDependency.CONTEXT).build(); private static final Symbol METADATA_TYPE = SymbolUtils.createValueSymbolBuilder( "Metadata", SmithyGoDependency.SMITHY_MIDDLEWARE).build(); private final Symbol middlewareSymbol; private final MiddlewareIdentifier middlewareId; private final String handleMethodName; private final Symbol inputType; private final Symbol outputType; private final Symbol handlerType; /** * Creates a new middleware generator with the given builder definition. * * @param builder the builder to create the generator with. */ public GoStackStepMiddlewareGenerator(Builder builder) { this.middlewareSymbol = SymbolUtils.createPointableSymbolBuilder(builder.name).build(); this.middlewareId = builder.id; this.handleMethodName = builder.handleMethodName; this.inputType = builder.inputType; this.outputType = builder.outputType; this.handlerType = builder.handlerType; } /** * Create a new InitializeStep middleware generator with the provided type name. * * @param name is the type name to identify the middleware. * @param id the unique ID for the middleware. * @return the middleware generator. */ public static GoStackStepMiddlewareGenerator createInitializeStepMiddleware(String name, MiddlewareIdentifier id) { return createMiddleware(name, id, "HandleInitialize", SymbolUtils.createValueSymbolBuilder("InitializeInput", SmithyGoDependency.SMITHY_MIDDLEWARE).build(), SymbolUtils.createValueSymbolBuilder("InitializeOutput", SmithyGoDependency.SMITHY_MIDDLEWARE).build(), SymbolUtils.createValueSymbolBuilder("InitializeHandler", SmithyGoDependency.SMITHY_MIDDLEWARE) .build()); } /** * Create an inline Initialize func. * * @param body is the function body. * @return the generated middleware func. */ public static GoWriter.Writable generateInitializeMiddlewareFunc(GoWriter.Writable body) { return goTemplate(""" func(ctx $T, in $T, next $T) ( out $T, metadata $T, err error, ) { $W } """, GoStdlibTypes.Context.Context, SmithyGoTypes.Middleware.InitializeInput, SmithyGoTypes.Middleware.InitializeHandler, SmithyGoTypes.Middleware.InitializeOutput, SmithyGoTypes.Middleware.Metadata, body); } /** * Create a new BuildStep middleware generator with the provided type name. * * @param name is the type name to identify the middleware. * @param id the unique ID for the middleware. * @return the middleware generator. */ public static GoStackStepMiddlewareGenerator createBuildStepMiddleware(String name, MiddlewareIdentifier id) { return createMiddleware(name, id, "HandleBuild", SymbolUtils.createValueSymbolBuilder("BuildInput", SmithyGoDependency.SMITHY_MIDDLEWARE).build(), SymbolUtils.createValueSymbolBuilder("BuildOutput", SmithyGoDependency.SMITHY_MIDDLEWARE).build(), SymbolUtils.createValueSymbolBuilder("BuildHandler", SmithyGoDependency.SMITHY_MIDDLEWARE).build()); } /** * Create a new SerializeStep middleware generator with the provided type name. * * @param name is the type name to identify the middleware. * @param id the unique ID for the middleware. * @return the middleware generator. */ public static GoStackStepMiddlewareGenerator createSerializeStepMiddleware(String name, MiddlewareIdentifier id) { return createMiddleware(name, id, "HandleSerialize", SymbolUtils.createValueSymbolBuilder("SerializeInput", SmithyGoDependency.SMITHY_MIDDLEWARE).build(), SymbolUtils.createValueSymbolBuilder("SerializeOutput", SmithyGoDependency.SMITHY_MIDDLEWARE).build(), SymbolUtils.createValueSymbolBuilder("SerializeHandler", SmithyGoDependency.SMITHY_MIDDLEWARE).build()); } /** * Create a new FinalizeStep middleware generator with the provided type name. * * @param name is the type name to identify the middleware. * @param id the unique ID for the middleware. * @return the middleware generator. */ public static GoStackStepMiddlewareGenerator createFinalizeStepMiddleware(String name, MiddlewareIdentifier id) { return createMiddleware(name, id, "HandleFinalize", SmithyGoTypes.Middleware.FinalizeInput, SmithyGoTypes.Middleware.FinalizeOutput, SmithyGoTypes.Middleware.FinalizeHandler); } /** * Create an inline Finalize func. * * @param body is the function body. * @return the generated middleware func. */ public static GoWriter.Writable generateFinalizeMiddlewareFunc(GoWriter.Writable body) { return goTemplate(""" func(ctx $T, in $T, next $T) ( out $T, metadata $T, err error, ) { $W } """, GoStdlibTypes.Context.Context, SmithyGoTypes.Middleware.FinalizeInput, SmithyGoTypes.Middleware.FinalizeHandler, SmithyGoTypes.Middleware.FinalizeOutput, SmithyGoTypes.Middleware.Metadata, body); } /** * Create a new DeserializeStep middleware generator with the provided type name. * * @param name is the type name to identify the middleware. * @param id the unique ID for the middleware. * @return the middleware generator. */ public static GoStackStepMiddlewareGenerator createDeserializeStepMiddleware(String name, MiddlewareIdentifier id) { return createMiddleware(name, id, "HandleDeserialize", SymbolUtils.createValueSymbolBuilder("DeserializeInput", SmithyGoDependency.SMITHY_MIDDLEWARE).build(), SymbolUtils.createValueSymbolBuilder("DeserializeOutput", SmithyGoDependency.SMITHY_MIDDLEWARE).build(), SymbolUtils.createValueSymbolBuilder("DeserializeHandler", SmithyGoDependency.SMITHY_MIDDLEWARE) .build()); } /** * Generates a new step middleware generator. * * @param name the name of the middleware type. * @param id the unique ID for the middleware. * @param handlerMethodName method name to be implemented. * @param inputType the middleware input type. * @param outputType the middleware output type. * @param handlerType the next handler type. * @return the middleware generator. */ public static GoStackStepMiddlewareGenerator createMiddleware( String name, MiddlewareIdentifier id, String handlerMethodName, Symbol inputType, Symbol outputType, Symbol handlerType ) { return builder() .name(name) .id(id) .handleMethodName(handlerMethodName) .inputType(inputType) .outputType(outputType) .handlerType(handlerType) .build(); } /** * Writes the middleware definition to the provided writer. * See the writeMiddleware overloaded function signature for a more complete definition. * * @param writer the writer to which the middleware definition will be written to. * @param handlerBodyConsumer is a consumer that will be call in the context of populating the handler definition. */ public void writeMiddleware( GoWriter writer, BiConsumer handlerBodyConsumer ) { writeMiddleware(writer, handlerBodyConsumer, (m, w) -> { }); } /** * Writes the middleware definition to the provided writer. *

* The following Go variables will be in scope of the handler body: * ctx - the Go standard library context.Context type. * in - the input for the given middleware type. * next - the next handler to be called. * out - the output for the given middleware type. * metadata - the smithy middleware.Metadata type. * err - the error interface type. * * @param writer the writer to which the middleware definition will be written to. * @param handlerBodyConsumer is a consumer that will be called in the context of populating the handler definition. * @param fieldConsumer is a consumer that will be called in the context of populating the struct members. */ public void writeMiddleware( GoWriter writer, BiConsumer handlerBodyConsumer, BiConsumer fieldConsumer ) { writer.addUseImports(CONTEXT_TYPE); writer.addUseImports(METADATA_TYPE); writer.addUseImports(inputType); writer.addUseImports(outputType); writer.addUseImports(handlerType); // generate the structure type definition for the middleware writer.openBlock("type $L struct {", "}", middlewareSymbol, () -> { fieldConsumer.accept(this, writer); }); writer.write(""); // each middleware step has to implement the ID function and return a unique string to identify itself with // here we return the name of the type writer.openBlock("func ($P) ID() string {", "}", middlewareSymbol, () -> { writer.writeInline("return "); middlewareId.writeInline(writer); writer.write(""); }); writer.write(""); // each middleware must implement their given handlerMethodName in order to satisfy the interface for // their respective step. writer.openBlock("func (m $P) $L(ctx $T, in $T, next $T) (\n" + "\tout $T, metadata $T, err error,\n" + ") {", "}", new Object[]{ middlewareSymbol, handleMethodName, CONTEXT_TYPE, inputType, handlerType, outputType, METADATA_TYPE, }, () -> { handlerBodyConsumer.accept(this, writer); }); } /** * Creates a Writable which renders the middleware. * @param body A Writable that renders the middleware body. * @param fields A Writable that renders the middleware struct's fields. * @return the writable. */ public GoWriter.Writable asWritable(GoWriter.Writable body, GoWriter.Writable fields) { return writer -> writeMiddleware( writer, (generator, bodyWriter) -> bodyWriter.write("$W", body), (generator, fieldWriter) -> fieldWriter.write("$W", fields) ); } /** * Returns a new middleware generator builder. * * @return the middleware generator builder. */ public static Builder builder() { return new Builder(); } /** * Get the handle method name. * * @return handler method name. */ public String getHandleMethodName() { return handleMethodName; } /** * Get the middleware type symbol. * * @return Symbol for the middleware type. */ public Symbol getMiddlewareSymbol() { return middlewareSymbol; } /** * Get the id of the middleware. * * @return id for the middleware. */ public MiddlewareIdentifier getMiddlewareId() { return middlewareId; } /** * Get the input type symbol reference. * * @return the input type symbol reference. */ public Symbol getInputType() { return inputType; } /** * Get the output type symbol reference. * * @return the output type symbol reference. */ public Symbol getOutputType() { return outputType; } /** * Get the handler type symbol reference. * * @return the handler type symbol reference. */ public Symbol getHandlerType() { return handlerType; } /** * Get the context type symbol. * * @return the context type symbol. */ public static Symbol getContextType() { return CONTEXT_TYPE; } /** * Get the middleware metadata type symbol. * * @return the middleware metadata type symbol. */ public static Symbol getMiddlewareMetadataType() { return METADATA_TYPE; } /** * Builds a {@link GoStackStepMiddlewareGenerator}. */ public static class Builder { private String name; private MiddlewareIdentifier id; private String handleMethodName; private Symbol inputType; private Symbol outputType; private Symbol handlerType; /** * Builds the middleware generator. * * @return the middleware generator. */ public GoStackStepMiddlewareGenerator build() { return new GoStackStepMiddlewareGenerator(this); } /** * Sets the handler method name. * * @param handleMethodName the middleware handler method name to implement. * @return the builder. */ public Builder handleMethodName(String handleMethodName) { this.handleMethodName = handleMethodName; return this; } /** * Set the name of the middleware type to be generated. * * @param name the name of the middleware type. * @return the builder. */ public Builder name(String name) { this.name = name; return this; } /** * Set the id for the middleware to be generated. * * @param id the middleware stack identifier. * @return the builder. */ public Builder id(MiddlewareIdentifier id) { this.id = id; return this; } /** * Set the input type symbol reference for the middleware. * * @param inputType the symbol reference to the input type. * @return the builder. */ public Builder inputType(Symbol inputType) { this.inputType = inputType; return this; } /** * Set the output type symbol reference for the middleware. * * @param outputType the symbol reference to the output type. * @return the builder. */ public Builder outputType(Symbol outputType) { this.outputType = outputType; return this; } /** * Set the handler type symbol reference for the middleware. * * @param handlerType the symbol reference to the handler type. * @return the builder. */ public Builder handlerType(Symbol handlerType) { this.handlerType = handlerType; return this; } } } GoStdlibTypes.java000066400000000000000000000071271463735525100351060ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import software.amazon.smithy.codegen.core.Symbol; /** * Collection of Symbol constants for types in the go standard library. */ @SuppressWarnings({"checkstyle:ConstantName", "checkstyle:LineLength"}) public final class GoStdlibTypes { private GoStdlibTypes() { } public static final class Bytes { public static final Symbol NewReader = SmithyGoDependency.BYTES.valueSymbol("NewReader"); } public static final class Context { public static final Symbol Context = SmithyGoDependency.CONTEXT.valueSymbol("Context"); public static final Symbol Background = SmithyGoDependency.CONTEXT.valueSymbol("Background"); } public static final class Time { public static final Symbol Time = SmithyGoDependency.TIME.pointableSymbol("Time"); } public static final class Encoding { public static final class Json { public static final Symbol NewDecoder = SmithyGoDependency.JSON.valueSymbol("NewDecoder"); public static final Symbol Number = SmithyGoDependency.JSON.valueSymbol("Number"); } public static final class Base64 { public static final Symbol StdEncoding = SmithyGoDependency.BASE64.valueSymbol("StdEncoding"); } } public static final class Crypto { public static final class Rand { public static final Symbol Reader = SmithyGoDependency.CRYPTORAND.valueSymbol("Reader"); } } public static final class Fmt { public static final Symbol Errorf = SmithyGoDependency.FMT.valueSymbol("Errorf"); public static final Symbol Sprintf = SmithyGoDependency.FMT.valueSymbol("Sprintf"); } public static final class Io { public static final Symbol ReadAll = SmithyGoDependency.IO.valueSymbol("ReadAll"); public static final Symbol Copy = SmithyGoDependency.IO.valueSymbol("Copy"); public static final class IoUtil { public static final Symbol Discard = SmithyGoDependency.IOUTIL.valueSymbol("Discard"); } } public static final class Net { public static final class Http { public static final Symbol Request = SmithyGoDependency.NET_HTTP.pointableSymbol("Request"); public static final Symbol Response = SmithyGoDependency.NET_HTTP.pointableSymbol("Response"); public static final Symbol Server = SmithyGoDependency.NET_HTTP.pointableSymbol("Server"); public static final Symbol Handler = SmithyGoDependency.NET_HTTP.valueSymbol("Handler"); public static final Symbol ResponseWriter = SmithyGoDependency.NET_HTTP.valueSymbol("ResponseWriter"); public static final Symbol MethodPost = SmithyGoDependency.NET_HTTP.valueSymbol("MethodPost"); } } public static final class Path { public static final Symbol Join = SmithyGoDependency.PATH.valueSymbol("Join"); } public static final class Testing { public static final Symbol T = SmithyGoDependency.TESTING.pointableSymbol("T"); } } GoUniverseTypes.java000066400000000000000000000046071463735525100354650ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import software.amazon.smithy.codegen.core.Symbol; /** * Collection of Symbol constants for golang universe types. * See predeclared identifiers. */ @SuppressWarnings("checkstyle:ConstantName") public final class GoUniverseTypes { public static final Symbol Any = universe("any"); public static final Symbol Bool = universe("bool"); public static final Symbol Byte = universe("byte"); public static final Symbol Comparable = universe("comparable"); public static final Symbol Complex64 = universe("complex64"); public static final Symbol Complex128 = universe("complex128"); public static final Symbol Error = universe("error"); public static final Symbol Float32 = universe("float32"); public static final Symbol Float64 = universe("float64"); public static final Symbol Int = universe("int"); public static final Symbol Int8 = universe("int8"); public static final Symbol Int16 = universe("int16"); public static final Symbol Int32 = universe("int32"); public static final Symbol Int64 = universe("int64"); public static final Symbol Rune = universe("rune"); public static final Symbol String = universe("string"); public static final Symbol Uint = universe("uint"); public static final Symbol Uint8 = universe("uint8"); public static final Symbol Uint16 = universe("uint16"); public static final Symbol Uint32 = universe("uint32"); public static final Symbol Uint64 = universe("uint64"); public static final Symbol Uintptr = universe("uintptr"); private GoUniverseTypes() {} private static Symbol universe(String name) { return SymbolUtils.createValueSymbolBuilder(name).putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true).build(); } } GoValueAccessUtils.java000066400000000000000000000312521463735525100360530ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. * * */ package software.amazon.smithy.go.codegen; import java.util.function.Consumer; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.CollectionShape; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.traits.EnumTrait; /** * Utilities for generating accessor checks around other generated blocks. */ public final class GoValueAccessUtils { private GoValueAccessUtils() { } /** * Writes non-zero conditional checks around a lambda specific to the member shape type. *

* Note: Collections and map member values by default will not have individual checks on member values. To check * not empty strings set the ignoreEmptyString to false. * * @param model smithy model * @param writer go writer * @param member API shape member to determine wrapping check with * @param operand string of text with access to value * @param ignoreEmptyString if empty strings also checked * @param ignoreUnboxedTypes if unboxed member types should be ignored * @param lambda lambda to run */ public static void writeIfNonZeroValue( Model model, GoWriter writer, MemberShape member, String operand, boolean ignoreEmptyString, boolean ignoreUnboxedTypes, Runnable lambda ) { Shape targetShape = model.expectShape(member.getTarget()); Shape container = model.expectShape(member.getContainer()); // default to empty block for variable scoping with not value check. String check = "{"; if (GoPointableIndex.of(model).isNillable(member)) { if (!ignoreEmptyString && targetShape.getType() == ShapeType.STRING) { check = String.format("if %s != nil && len(*%s) > 0 {", operand, operand); } else { check = String.format("if %s != nil {", operand); } } else if (container instanceof CollectionShape || container.getType() == ShapeType.MAP) { if (!ignoreEmptyString && targetShape.getType() == ShapeType.STRING) { check = String.format("if len(%s) > 0 {", operand); } else if (!ignoreEmptyString && targetShape.getType() == ShapeType.ENUM) { check = String.format("if len(%s) > 0 {", operand); } } else if (targetShape.hasTrait(EnumTrait.class)) { check = String.format("if len(%s) > 0 {", operand); } else if (!ignoreUnboxedTypes && targetShape.getType() == ShapeType.BOOLEAN) { check = String.format("if %s {", operand); } else if (!ignoreUnboxedTypes && CodegenUtils.isNumber(targetShape)) { check = String.format("if %s != 0 {", operand); } else if (!ignoreEmptyString && targetShape.getType() == ShapeType.STRING) { check = String.format("if len(%s) > 0 {", operand); } writer.openBlock(check, "}", lambda); } /** * Writes non-zero conditional checks around a lambda specific to the member shape type. *

* Ignores empty strings of string pointers, and nested within list and maps. * * @param model smithy model * @param writer go writer * @param member API shape member to determine wrapping check with * @param operand string of text with access to value * @param lambda lambda to run */ public static void writeIfNonZeroValue( Model model, GoWriter writer, MemberShape member, String operand, Runnable lambda ) { writeIfNonZeroValue(model, writer, member, operand, true, false, lambda); } /** * Writes non-zero conditional check around a lambda specific to a member of a container. *

* Ignores empty strings of string pointers, and members nested within list and maps. * * @param model smithy model * @param symbolProvider symbol provider * @param writer go writer * @param member API shape member to determine wrapping check with * @param container operand of source member is a part of. * @param lambda lambda to run */ public static void writeIfNonZeroValueMember( Model model, SymbolProvider symbolProvider, GoWriter writer, MemberShape member, String container, Consumer lambda ) { writeIfNonZeroValueMember(model, symbolProvider, writer, member, container, true, false, lambda); } /** * Writes non-zero conditional check around a lambda specific to a member of a container. *

* Note: Collections and map member values by default will not have individual checks on member values. To check * not empty strings set the ignoreEmptyString to false. * * @param model smithy model * @param symbolProvider symbol provider * @param writer go writer * @param member API shape member to determine wrapping check with * @param container operand of source member is a part of. * @param ignoreEmptyString if empty strings also checked * @param ignoreUnboxedTypes if unboxed member types should be ignored * @param lambda lambda to run */ public static void writeIfNonZeroValueMember( Model model, SymbolProvider symbolProvider, GoWriter writer, MemberShape member, String container, boolean ignoreEmptyString, boolean ignoreUnboxedTypes, Consumer lambda ) { String memberName = symbolProvider.toMemberName(member); String operand = container + "." + memberName; writeIfNonZeroValue(model, writer, member, operand, ignoreEmptyString, ignoreUnboxedTypes, () -> { lambda.accept(operand); }); } /** * Writes zero conditional checks around a lambda specific to the member shape type. *

* Members with containers of Collection and map shapes, will ignore the lambda block * and not call it. Optionally will ignore empty strings based on the ignoreEmptyString flag. *

* Non-nillable shapes other than Enum, Boolean, and Number will ignore the lambda block. Optionally will ignore * empty strings based on the ignoreEmptyString flag. *

* Note: Collections and map member values by default will not have individual checks on member values. To check * for empty strings set the ignoreEmptyString to false. * * @param model smithy model * @param writer go writer * @param member API shape member to determine wrapping check with * @param operand string of text with access to value * @param ignoreEmptyString if empty strings also checked * @param ignoreUnboxedTypes if unboxed member types should be ignored * @param lambda lambda to run */ public static void writeIfZeroValue( Model model, GoWriter writer, MemberShape member, String operand, boolean ignoreEmptyString, boolean ignoreUnboxedTypes, Runnable lambda ) { Shape targetShape = model.expectShape(member.getTarget()); Shape container = model.expectShape(member.getContainer()); String check = "{"; if (GoPointableIndex.of(model).isNillable(member)) { if (!ignoreEmptyString && targetShape.getType() == ShapeType.STRING) { check = String.format("if %s == nil || len(*%s) == 0 {", operand, operand); } else { check = String.format("if %s == nil {", operand); } } else if (container instanceof CollectionShape || container.getType() == ShapeType.MAP) { // Always serialize values in map/list/sets, no additional check, which means that the // lambda will not be run, because there is no zero value to check against. if (!ignoreEmptyString && targetShape.getType() == ShapeType.STRING) { check = String.format("if len(%s) == 0 {", operand); } else { return; } } else if (targetShape.hasTrait(EnumTrait.class)) { check = String.format("if len(%s) == 0 {", operand); } else if (!ignoreUnboxedTypes && targetShape.getType() == ShapeType.BOOLEAN) { check = String.format("if !%s {", operand); } else if (!ignoreUnboxedTypes && CodegenUtils.isNumber(targetShape)) { check = String.format("if %s == 0 {", operand); } else if (!ignoreEmptyString && targetShape.getType() == ShapeType.STRING) { check = String.format("if len(%s) == 0 {", operand); } else { // default to empty block for variable scoping with not value check. return; } writer.openBlock(check, "}", lambda); } /** * Writes zero conditional checks around a lambda specific to the member shape type. *

* Ignores empty strings of string pointers, and members nested within list and maps. * * @param model smithy model * @param writer go writer * @param member API shape member to determine wrapping check with * @param operand string of text with access to value * @param lambda lambda to run */ public static void writeIfZeroValue( Model model, GoWriter writer, MemberShape member, String operand, Runnable lambda ) { writeIfZeroValue(model, writer, member, operand, true, false, lambda); } /** * Writes zero conditional check around a lambda specific to a member of a container. *

* Ignores empty strings of string pointers, and members nested within list and maps. * * @param model smithy model * @param symbolProvider symbol provider * @param writer go writer * @param member API shape member to determine wrapping check with * @param container operand of source member is a part of. * @param lambda lambda to run */ public static void writeIfZeroValueMember( Model model, SymbolProvider symbolProvider, GoWriter writer, MemberShape member, String container, Consumer lambda ) { writeIfZeroValueMember(model, symbolProvider, writer, member, container, true, false, lambda); } /** * Writes zero conditional check around a lambda specific to a member of a container. *

* Ignores empty strings of string pointers, and members nested within list and maps. * * @param model smithy model * @param symbolProvider symbol provider * @param writer go writer * @param member API shape member to determine wrapping check with * @param container operand of source member is a part of. * @param ignoreEmptyString if empty strings also checked * @param ignoreUnboxedTypes if unboxed member types should be ignored * @param lambda lambda to run */ public static void writeIfZeroValueMember( Model model, SymbolProvider symbolProvider, GoWriter writer, MemberShape member, String container, boolean ignoreEmptyString, boolean ignoreUnboxedTypes, Consumer lambda ) { String memberName = symbolProvider.toMemberName(member); String operand = container + "." + memberName; writeIfZeroValue(model, writer, member, operand, ignoreEmptyString, ignoreUnboxedTypes, () -> { lambda.accept(operand); }); } } GoWriter.java000066400000000000000000001114511463735525100341100ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.logging.Logger; import java.util.regex.Pattern; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolContainer; import software.amazon.smithy.codegen.core.SymbolReference; import software.amazon.smithy.codegen.core.SymbolWriter; import software.amazon.smithy.go.codegen.knowledge.GoUsageIndex; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.Prelude; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.traits.DeprecatedTrait; import software.amazon.smithy.model.traits.DocumentationTrait; import software.amazon.smithy.model.traits.HttpPrefixHeadersTrait; import software.amazon.smithy.model.traits.MediaTypeTrait; import software.amazon.smithy.model.traits.RequiredTrait; import software.amazon.smithy.model.traits.StringTrait; import software.amazon.smithy.utils.AbstractCodeWriter; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.SmithyInternalApi; import software.amazon.smithy.utils.StringUtils; /** * Specialized code writer for managing Go dependencies. * *

Use the {@code $T} formatter to refer to {@link Symbol}s without using pointers. * *

Use the {@code $P} formatter to refer to {@link Symbol}s using pointers where appropriate. */ @SmithyInternalApi public final class GoWriter extends SymbolWriter { private static final Logger LOGGER = Logger.getLogger(GoWriter.class.getName()); private static final int DEFAULT_DOC_WRAP_LENGTH = 80; private static final Pattern ARGUMENT_NAME_PATTERN = Pattern.compile("\\$([a-z][a-zA-Z_0-9]+)(:\\w)?"); private final String fullPackageName; private final boolean innerWriter; private final List buildTags = new ArrayList<>(); private int docWrapLength = DEFAULT_DOC_WRAP_LENGTH; private AbstractCodeWriter packageDocs; /** * Initializes the GoWriter for the package and filename to be written to. * * @param fullPackageName package and filename to be written to. */ public GoWriter(String fullPackageName) { super(new ImportDeclarations(fullPackageName)); this.fullPackageName = fullPackageName; this.innerWriter = false; init(); } private GoWriter(String fullPackageName, boolean innerWriter) { super(new ImportDeclarations(fullPackageName)); this.fullPackageName = fullPackageName; this.innerWriter = innerWriter; init(); } private void init() { trimBlankLines(); trimTrailingSpaces(); setIndentText("\t"); putFormatter('T', new GoSymbolFormatter()); putFormatter('P', new PointableGoSymbolFormatter()); putFormatter('W', new GoWritableInjector()); putFormatter('D', new GoDependencyFormatter()); if (!innerWriter) { packageDocs = new GoWriter(this.fullPackageName, true); } } // TODO figure out better way to annotate where the failure occurs, check templates and args // TODO to try to find programming bugs. /** * Joins multiple writables together within a single writable without newlines between writables in the list. * * @param writables list of writables to join * @param separator separator between writables * @return new writable */ public static Writable joinWritables(List writables, String separator) { return (GoWriter w) -> { for (int i = 0; i < writables.size(); i++) { var writable = writables.get(i); var sep = separator; if (i == writables.size() - 1) { sep = ""; } w.writeInline("$W" + sep, writable); } }; } /** * Returns a Writable for the string and args to be composed inline to another writer's contents. * * @param contents string to write. * @param args Arguments to use when evaluating the contents string. * @return Writable to be evaluated. */ @SafeVarargs public static Writable goTemplate(String contents, Map... args) { validateTemplateArgsNotNull(args); return (GoWriter w) -> { w.writeGoTemplate(contents, args); }; } /** * Returns a Writable for the string and args to be composed inline to another writer's contents. * * @param content string to write. * @param args Arguments to use when evaluating the contents string. * @return Writable to be evaluated. */ public static Writable goTemplate(Object content, Object... args) { return writer -> writer.write(content, args); } public static Writable goDocTemplate(String contents) { return goDocTemplate(contents, new HashMap<>()); } /** * Auto-formats a multi-paragraph string as a doc writable (including line wrapping). * @param contents The docs. * @return writer for formatted docs. */ public static Writable autoDocTemplate(String contents) { var paragraphs = contents.split("\n\n"); var chain = new GoWriter.ChainWritable(); for (int i = 0; i < paragraphs.length; ++i) { chain.add(docParagraphWriter(paragraphs[i], i < paragraphs.length - 1)); } return chain.compose(false); } private static GoWriter.Writable docParagraphWriter(String paragraph, boolean writeNewline) { return writer -> { writer.writeDocs(paragraph); if (writeNewline) { writer.writeDocs(""); } }; } @SafeVarargs public static Writable goDocTemplate(String contents, Map... args) { validateTemplateArgsNotNull(args); return (GoWriter w) -> { w.writeGoDocTemplate(contents, args); }; } /** * Returns a Writable that can later be invoked to write the contents as template * as a code block instead of single content of text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param fn closure to write */ public static Writable goBlockTemplate( String beforeNewLine, String afterNewLine, Consumer fn ) { return goBlockTemplate(beforeNewLine, afterNewLine, new Map[0], fn); } /** * Returns a Writable that can later be invoked to write the contents as template * as a code block instead of single content of text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param args1 template arguments * @param fn closure to write */ public static Writable goBlockTemplate( String beforeNewLine, String afterNewLine, Map args1, Consumer fn ) { return goBlockTemplate(beforeNewLine, afterNewLine, new Map[]{args1}, fn); } /** * Returns a Writable that can later be invoked to write the contents as template * as a code block instead of single content of text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param args1 template arguments * @param args2 template arguments * @param fn closure to write */ public static Writable goBlockTemplate( String beforeNewLine, String afterNewLine, Map args1, Map args2, Consumer fn ) { return goBlockTemplate(beforeNewLine, afterNewLine, new Map[]{args1, args2}, fn); } /** * Returns a Writable that can later be invoked to write the contents as template * as a code block instead of single content of text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param args1 template arguments * @param args2 template arguments * @param args3 template arguments * @param fn closure to write */ public static Writable goBlockTemplate( String beforeNewLine, String afterNewLine, Map args1, Map args2, Map args3, Consumer fn ) { return goBlockTemplate(beforeNewLine, afterNewLine, new Map[]{args1, args2, args3}, fn); } /** * Returns a Writable that can later be invoked to write the contents as template * as a code block instead of single content of text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param args1 template arguments * @param args2 template arguments * @param args3 template arguments * @param args4 template arguments * @param fn closure to write */ public static Writable goBlockTemplate( String beforeNewLine, String afterNewLine, Map args1, Map args2, Map args3, Map args4, Consumer fn ) { return goBlockTemplate(beforeNewLine, afterNewLine, new Map[]{args1, args2, args3, args4}, fn); } /** * Returns a Writable that can later be invoked to write the contents as template * as a code block instead of single content of text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param args1 template arguments * @param args2 template arguments * @param args3 template arguments * @param args4 template arguments * @param args5 template arguments * @param fn closure to write */ public static Writable goBlockTemplate( String beforeNewLine, String afterNewLine, Map args1, Map args2, Map args3, Map args4, Map args5, Consumer fn ) { return goBlockTemplate(beforeNewLine, afterNewLine, new Map[]{args1, args2, args3, args4, args5}, fn); } /** * Returns a Writable that can later be invoked to write the contents as template * as a code block instead of single content of text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param args template arguments * @param fn closure to write */ public static Writable goBlockTemplate( String beforeNewLine, String afterNewLine, Map[] args, Consumer fn ) { validateTemplateArgsNotNull(args); return (GoWriter w) -> { w.writeGoBlockTemplate(beforeNewLine, afterNewLine, args, fn); }; } /** * Returns a Writable that does nothing. * * @return Writable that does nothing */ public static Writable emptyGoTemplate() { return (GoWriter w) -> { }; } /** * Writes the contents and arguments as a template to the writer. * * @param contents string to write * @param args Arguments to use when evaluating the contents string. */ @SafeVarargs public final void writeGoTemplate(String contents, Map... args) { withTemplate(contents, args, (template) -> { try { write(contents); } catch (Exception e) { throw new CodegenException("Failed to render template\n" + contents + "\nReason: " + e.getMessage(), e); } }); } @SafeVarargs public final void writeGoDocTemplate(String contents, Map... args) { writeRenderedDocs(goTemplate(contents, args)); } /** * Writes the contents as template as a code block instead of single content fo text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param fn closure to write */ public void writeGoBlockTemplate( String beforeNewLine, String afterNewLine, Consumer fn ) { writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[0], fn); } /** * Writes the contents as template as a code block instead of single content fo text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param arg1 first map argument * @param fn closure to write */ public void writeGoBlockTemplate( String beforeNewLine, String afterNewLine, Map arg1, Consumer fn ) { writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[]{arg1}, fn); } /** * Writes the contents as template as a code block instead of single content fo text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param arg1 first map argument * @param arg2 second map argument * @param fn closure to write */ public void writeGoBlockTemplate( String beforeNewLine, String afterNewLine, Map arg1, Map arg2, Consumer fn ) { writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[]{arg1, arg2}, fn); } /** * Writes the contents as template as a code block instead of single content fo text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param arg1 first map argument * @param arg2 second map argument * @param arg3 third map argument * @param fn closure to write */ public void writeGoBlockTemplate( String beforeNewLine, String afterNewLine, Map arg1, Map arg2, Map arg3, Consumer fn ) { writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[]{arg1, arg2, arg3}, fn); } /** * Writes the contents as template as a code block instead of single content fo text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param arg1 first map argument * @param arg2 second map argument * @param arg3 third map argument * @param arg4 forth map argument * @param fn closure to write */ public void writeGoBlockTemplate( String beforeNewLine, String afterNewLine, Map arg1, Map arg2, Map arg3, Map arg4, Consumer fn ) { writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[]{arg1, arg2, arg3, arg4}, fn); } /** * Writes the contents as template as a code block instead of single content fo text. * * @param beforeNewLine text before new line * @param afterNewLine text after new line * @param arg1 first map argument * @param arg2 second map argument * @param arg3 third map argument * @param arg4 forth map argument * @param arg5 forth map argument * @param fn closure to write */ public void writeGoBlockTemplate( String beforeNewLine, String afterNewLine, Map arg1, Map arg2, Map arg3, Map arg4, Map arg5, Consumer fn ) { writeGoBlockTemplate(beforeNewLine, afterNewLine, new Map[]{arg1, arg2, arg3, arg4, arg5}, fn); } public void writeGoBlockTemplate( String beforeNewLine, String afterNewLine, Map[] args, Consumer fn ) { withTemplate(beforeNewLine, args, (header) -> { conditionalBlock(header, afterNewLine, true, new Object[0], fn); }); } private void withTemplate( String template, Map[] argMaps, Consumer fn ) { pushState(); for (var args : argMaps) { putContext(args); } validateContext(template); fn.accept(template); popState(); } private GoWriter conditionalBlock( String beforeNewLine, String afterNewLine, boolean conditional, Object[] args, Consumer fn ) { if (conditional) { openBlock(beforeNewLine.trim(), args); } fn.accept(this); if (conditional) { closeBlock(afterNewLine.trim()); } return this; } private static void validateTemplateArgsNotNull(Map[] argMaps) { for (var args : argMaps) { args.forEach((k, v) -> { if (v == null) { throw new CodegenException("Template argument " + k + " cannot be null"); } }); } } private void validateContext(String template) { var matcher = ARGUMENT_NAME_PATTERN.matcher(template); while (matcher.find()) { var keyName = matcher.group(1); var value = getContext(keyName); if (value == null) { throw new CodegenException( "Go template expected " + keyName + " but was not present in context scope." + " Template: \n" + template); } } } /** * Sets the wrap length of doc strings written. * * @param wrapLength The wrap length of the doc string. * @return Returns the writer. */ public GoWriter setDocWrapLength(final int wrapLength) { this.docWrapLength = wrapLength; return this; } /** * Sets the wrap length of doc strings written to the default value for the Go writer. * * @return Returns the writer. */ public GoWriter setDocWrapLength() { this.docWrapLength = DEFAULT_DOC_WRAP_LENGTH; return this; } /** * Imports one or more symbols if necessary, using the name of the * symbol and only "USE" references. * * @param container Container of symbols to add. * @return Returns the writer. */ public GoWriter addUseImports(SymbolContainer container) { for (Symbol symbol : container.getSymbols()) { addImport(symbol, CodegenUtils.getSymbolNamespaceAlias(symbol), SymbolReference.ContextOption.USE); } return this; } /** * Imports a symbol reference if necessary, using the alias of the * reference and only associated "USE" references. * * @param symbolReference Symbol reference to import. * @return Returns the writer. */ public GoWriter addUseImports(SymbolReference symbolReference) { return addImport(symbolReference.getSymbol(), symbolReference.getAlias(), SymbolReference.ContextOption.USE); } /** * Adds and imports the given dependency. * * @param goDependency The GoDependency to import. * @return Returns the writer. */ public GoWriter addUseImports(GoDependency goDependency) { addDependency(goDependency); return addImport(goDependency.getImportPath(), goDependency.getAlias()); } private void addImports(GoWriter other) { this.getImportContainer().addImports(other.getImportContainer()); } private boolean isExternalNamespace(String namespace) { return !StringUtils.isBlank(namespace) && !namespace.equals(fullPackageName); } void addImportReferences(Symbol symbol, SymbolReference.ContextOption... options) { for (SymbolReference reference : symbol.getReferences()) { for (SymbolReference.ContextOption option : options) { if (reference.hasOption(option)) { addImport(reference.getSymbol(), reference.getAlias(), options); break; } } } } /** * Imports a package using an alias if necessary. * * @param packageName Package to import. * @param as Alias to refer to the package as. * @return Returns the writer. */ public GoWriter addImport(String packageName, String as) { getImportContainer().addImport(packageName, as); return this; } private void addDependencies(GoWriter other) { addDependency(other); } /** * Writes documentation comments. * * @param runnable Runnable that handles actually writing docs with the writer. * @return Returns the writer. */ private void writeDocs(AbstractCodeWriter writer, Runnable runnable) { writer.pushState("docs"); writer.setNewlinePrefix("// "); runnable.run(); writer.setNewlinePrefix(""); writer.popState(); } private void writeDocs(AbstractCodeWriter writer, int docWrapLength, String docs) { String convertedDoc = DocumentationConverter.convert(docs, docWrapLength); writeDocs(writer, () -> writer.write(convertedDoc.replace("$", "$$"))); } /** * Writes documentation comments from a string. * *

This function escapes "$" characters so formatters are not run. * * @param docs Documentation to write. * @return Returns the writer. */ public GoWriter writeDocs(String docs) { writeDocs(this, docWrapLength, docs); return this; } /** * Writes documentation from an arbitrary Writable. * * @param writable Contents to be written. * @return Returns the writer. */ public GoWriter writeRenderedDocs(Writable writable) { writeRenderedDocs(this, docWrapLength, writable); return this; } private void writeRenderedDocs(AbstractCodeWriter writer, int docWrapLength, Writable writable) { var innerWriter = new GoWriter(fullPackageName, true); writable.accept(innerWriter); var wrappedDocs = StringUtils.wrap(innerWriter.toString().trim(), docWrapLength); writeDocs(writer, () -> writer.write(wrappedDocs.replace("$", "$$"))); } /** * Writes the doc to the Go package docs that are written prior to the go package statement. * * @param docs documentation to write to package doc. * @return writer */ public GoWriter writePackageDocs(String docs) { writeDocs(packageDocs, docWrapLength, docs); return this; } /** * Writes the doc to the Go package docs that are written prior to the go package statement. This does not perform * line wrapping and the provided formatting must be valid Go doc. * * @param docs documentation to write to package doc. * @return writer */ public GoWriter writeRawPackageDocs(String docs) { writeDocs(packageDocs, () -> { packageDocs.write(docs); }); return this; } /** * Writes shape documentation comments if docs are present. * * @param shape Shape to write the documentation of. * @return Returns true if docs were written. */ @SmithyInternalApi public boolean writeShapeDocs(Shape shape) { return shape.getTrait(DocumentationTrait.class) .map(DocumentationTrait::getValue) .map(docs -> { writeDocs(docs); return true; }).orElse(false); } /** * Writes shape documentation comments to the writer's package doc if docs are present. * * @param shape Shape to write the documentation of. * @return Returns true if docs were written. */ public boolean writePackageShapeDocs(Shape shape) { return shape.getTrait(DocumentationTrait.class) .map(DocumentationTrait::getValue) .map(docs -> { writePackageDocs(docs); return true; }).orElse(false); } /** * Writes member shape documentation comments if docs are present. * * @param model Model used to dereference targets. * @param member Shape to write the documentation of. * @return Returns true if docs were written. */ public boolean writeMemberDocs(Model model, MemberShape member) { boolean hasDocs; hasDocs = member.getMemberTrait(model, DocumentationTrait.class) .map(DocumentationTrait::getValue) .map(docs -> { writeDocs(docs); return true; }).orElse(false); Optional stringOptional = member.getMemberTrait(model, MediaTypeTrait.class) .map(StringTrait::getValue); if (stringOptional.isPresent()) { if (hasDocs) { writeDocs(""); } writeDocs("This value conforms to the media type: " + stringOptional.get()); hasDocs = true; } GoUsageIndex usageIndex = GoUsageIndex.of(model); if (usageIndex.isUsedForOutput(member)) { if (member.getMemberTrait(model, HttpPrefixHeadersTrait.class).isPresent()) { if (hasDocs) { writeDocs(""); } writeDocs("Map keys will be normalized to lower-case."); hasDocs = true; } } if (member.getMemberTrait(model, RequiredTrait.class).isPresent()) { if (hasDocs) { writeDocs(""); } writeDocs("This member is required."); hasDocs = true; } Optional deprecatedTrait = member.getMemberTrait(model, DeprecatedTrait.class); if (member.getTrait(DeprecatedTrait.class).isPresent() || isTargetDeprecated(model, member)) { if (hasDocs) { writeDocs(""); } final String defaultMessage = "This member has been deprecated."; String message = defaultMessage; if (deprecatedTrait.isPresent()) { message = deprecatedTrait.get().getMessage().map(s -> { if (s.length() == 0) { return defaultMessage; } return s; }).orElse(defaultMessage); } writeDocs("Deprecated: " + message); } return hasDocs; } private boolean isTargetDeprecated(Model model, MemberShape member) { return model.expectShape(member.getTarget()).getTrait(DeprecatedTrait.class).isPresent() // don't consider deprecated prelude shapes (like PrimitiveBoolean) && !Prelude.isPreludeShape(member.getTarget()); } public void write(Writable w) { write("$W", w); } public GoWriter addBuildTag(String tag) { buildTags.add(tag); return this; } @Override public String toString() { String contents = super.toString(); if (innerWriter) { return contents; } var tags = buildTags.isEmpty() ? "" : "//go:build " + String.join(",", buildTags) + "\n"; String[] packageParts = fullPackageName.split("/"); String header = String.format("// Code generated by smithy-go-codegen DO NOT EDIT.%n%n"); String packageName = packageParts[packageParts.length - 1]; if (packageName.startsWith("v") && packageParts.length >= 2) { String remaining = packageName.substring(1); try { int value = Integer.parseInt(remaining); packageName = packageParts[packageParts.length - 2]; if (value == 0 || value == 1) { throw new CodegenException("module paths vN version component must only be N >= 2"); } } catch (NumberFormatException ne) { // Do nothing } } String packageDocs = this.packageDocs.toString(); String packageStatement = String.format("package %s%n%n", packageName); String importString = getImportContainer().toString(); String strippedContents = StringUtils.stripStart(contents, null); String strippedImportString = StringUtils.strip(importString, null); // Don't add an additional new line between explicit imports and managed imports. if (!strippedImportString.isEmpty() && strippedContents.startsWith("import ")) { return header + strippedImportString + "\n" + strippedContents; } return header + packageDocs + tags + packageStatement + importString + contents; } /** * Implements Go symbol formatting for the {@code $T} formatter. */ private class GoSymbolFormatter implements BiFunction { @Override public String apply(Object type, String indent) { if (type instanceof Symbol) { Symbol resolvedSymbol = (Symbol) type; String literal = resolvedSymbol.getName(); boolean isSlice = resolvedSymbol.getProperty(SymbolUtils.GO_SLICE, Boolean.class).orElse(false); boolean isMap = resolvedSymbol.getProperty(SymbolUtils.GO_MAP, Boolean.class).orElse(false); if (isSlice || isMap) { resolvedSymbol = resolvedSymbol.getProperty(SymbolUtils.GO_ELEMENT_TYPE, Symbol.class) .orElseThrow(() -> new CodegenException("Expected go element type property to be defined")); literal = new PointableGoSymbolFormatter().apply(resolvedSymbol, "nested"); } else if (!SymbolUtils.isUniverseType(resolvedSymbol) && isExternalNamespace(resolvedSymbol.getNamespace())) { literal = formatWithNamespace(resolvedSymbol); } addUseImports(resolvedSymbol); if (isSlice) { return "[]" + literal; } else if (isMap) { return "map[string]" + literal; } else { return literal; } } else if (type instanceof SymbolReference) { SymbolReference typeSymbol = (SymbolReference) type; addImport(typeSymbol.getSymbol(), typeSymbol.getAlias(), SymbolReference.ContextOption.USE); return typeSymbol.getAlias(); } else { throw new CodegenException( "Invalid type provided to $T. Expected a Symbol, but found `" + type + "`"); } } private String formatWithNamespace(Symbol symbol) { if (StringUtils.isEmpty(symbol.getNamespace())) { return symbol.getName(); } return String.format("%s.%s", CodegenUtils.getSymbolNamespaceAlias(symbol), symbol.getName()); } } /** * Implements Go symbol formatting for the {@code $P} formatter. This is identical to the $T * formatter, except that it will add a * to symbols that can be pointers. */ private class PointableGoSymbolFormatter extends GoSymbolFormatter { @Override public String apply(Object type, String indent) { String formatted = super.apply(type, indent); if (isPointer(type)) { formatted = "*" + formatted; } return formatted; } private boolean isPointer(Object type) { if (type instanceof Symbol) { Symbol typeSymbol = (Symbol) type; return typeSymbol.getProperty(SymbolUtils.POINTABLE, Boolean.class).orElse(false); } else if (type instanceof SymbolReference) { SymbolReference typeSymbol = (SymbolReference) type; return typeSymbol.getProperty(SymbolUtils.POINTABLE, Boolean.class).orElse(false) || typeSymbol.getSymbol().getProperty(SymbolUtils.POINTABLE, Boolean.class).orElse(false); } else { throw new CodegenException( "Invalid type provided to $P. Expected a Symbol, but found `" + type + "`"); } } } class GoWritableInjector extends GoSymbolFormatter { @Override public String apply(Object type, String indent) { if (!(type instanceof Writable)) { throw new CodegenException( "expect Writable for GoWriter W injector, but got " + type); } var innerWriter = new GoWriter(fullPackageName, true); ((Writable) type).accept(innerWriter); addImports(innerWriter); addDependencies(innerWriter); return innerWriter.toString().trim(); } } /** * Implements Go symbol formatting for the {@code $D} formatter. */ private class GoDependencyFormatter implements BiFunction { @Override public String apply(Object type, String indent) { if (type instanceof GoDependency) { addUseImports((GoDependency) type); } else { throw new CodegenException( "Invalid type provided to $D. Expected a GoDependency, but found `" + type + "`"); } return ""; } } public interface Writable extends Consumer { } /** * Chains together multiple Writables that can be composed into one Writable. */ public static final class ChainWritable { private final List writables; public ChainWritable() { writables = new ArrayList<>(); } public static ChainWritable of(GoWriter.Writable... writables) { var chain = new ChainWritable(); chain.writables.addAll(ListUtils.of(writables)); return chain; } public static ChainWritable of(Collection writables) { var chain = new ChainWritable(); chain.writables.addAll(writables); return chain; } public boolean isEmpty() { return writables.isEmpty(); } public ChainWritable add(GoWriter.Writable writable) { writables.add(writable); return this; } public ChainWritable add(Optional value, Function fn) { value.ifPresent(t -> writables.add(fn.apply(t))); return this; } public ChainWritable add(boolean include, GoWriter.Writable writable) { if (!include) { writables.add(writable); } return this; } public GoWriter.Writable compose(boolean writeNewlines) { return (GoWriter writer) -> { var hasPrevious = false; for (GoWriter.Writable writable : writables) { if (hasPrevious && writeNewlines) { writer.write(""); } hasPrevious = true; writer.write("$W", writable); } }; } public GoWriter.Writable compose() { return compose(true); } } } ImportDeclarations.java000066400000000000000000000065711463735525100361570ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.Map; import java.util.TreeMap; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.ImportContainer; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.utils.StringUtils; /** * Container and formatter for go imports. */ final class ImportDeclarations implements ImportContainer { private final String packageName; private final Map imports = new TreeMap<>(); ImportDeclarations(String packageName) { this.packageName = packageName; } public void addImport(String importPath, String alias) { if (importPath.isBlank() || importPath.equals(packageName)) { return; // either a universe type or something from the local package } String importAlias = CodegenUtils.getDefaultPackageImportName(importPath); if (!StringUtils.isBlank(alias)) { if (alias.equals(".")) { // Global imports are generally a bad practice. throw new CodegenException("Globally importing packages is forbidden: " + importPath); } importAlias = alias; } // Ensure that multiple packages cannot be imported with the same name. if (imports.containsKey(importAlias) && !imports.get(importAlias).equals(importPath)) { throw new CodegenException("Import name collision: " + importAlias + ". Previous: " + imports.get(importAlias) + "New: " + importPath); } imports.putIfAbsent(importAlias, importPath); } public void addImports(ImportDeclarations other) { other.imports.forEach((importAlias, importPath) -> { addImport(importPath, importAlias); }); } @Override public void importSymbol(Symbol symbol, String alias) { if (!symbol.getNamespace().isBlank()) { // e.g. a universe type like string addImport(symbol.getNamespace(), alias); } } @Override public String toString() { if (imports.isEmpty()) { return ""; } StringBuilder builder = new StringBuilder("import (\n"); for (Map.Entry entry : imports.entrySet()) { builder.append('\t'); builder.append(createImportStatement(entry)); builder.append('\n'); } builder.append(")\n\n"); return builder.toString(); } private String createImportStatement(Map.Entry entry) { String formattedPackageName = "\"" + entry.getValue() + "\""; return CodegenUtils.getDefaultPackageImportName(entry.getValue()).equals(entry.getKey()) ? formattedPackageName : entry.getKey() + " " + formattedPackageName; } } IntEnumGenerator.java000066400000000000000000000113241463735525100355720ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.model.shapes.IntEnumShape; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.traits.DocumentationTrait; import software.amazon.smithy.model.traits.EnumValueTrait; import software.amazon.smithy.utils.SmithyInternalApi; import software.amazon.smithy.utils.StringUtils; /** * Renders intEnums and their constants. */ @SmithyInternalApi public final class IntEnumGenerator implements Runnable { private static final Logger LOGGER = Logger.getLogger(IntEnumGenerator.class.getName()); private final SymbolProvider symbolProvider; private final GoWriter writer; private final IntEnumShape shape; public IntEnumGenerator(SymbolProvider symbolProvider, GoWriter writer, IntEnumShape shape) { this.symbolProvider = symbolProvider; this.writer = writer; this.shape = shape; } @Override public void run() { Symbol symbol = symbolProvider.toSymbol(shape); // TODO(smithy): Use type alias instead of type definition until refactoring // protocol generators is prioritized. writer.write("type $L = int32", symbol.getName()).write(""); writer.writeDocs(String.format("Enum values for %s", symbol.getName())); Set constants = new LinkedHashSet<>(); writer.openBlock("const (", ")", () -> { for (Map.Entry entry : shape.getAllMembers().entrySet()) { StringBuilder labelBuilder = new StringBuilder(symbol.getName()); String name = entry.getKey(); for (String part : name.split("(?U)[\\W_]")) { if (part.matches(".*[a-z].*") && part.matches(".*[A-Z].*")) { // Mixed case names should not be changed other than first letter capitalized. labelBuilder.append(StringUtils.capitalize(part)); } else { // For all non-mixed case parts title case first letter, followed by all other lower cased. labelBuilder.append(StringUtils.capitalize(part.toLowerCase(Locale.US))); } } String label = labelBuilder.toString(); // If camel-casing would cause a conflict, don't camel-case this enum value. if (constants.contains(label)) { LOGGER.warning(String.format( "Multiple enums resolved to the same name, `%s`, using unaltered value for: %s", label, name)); label = name; } constants.add(label); entry.getValue().getTrait(DocumentationTrait.class) .ifPresent(trait -> writer.writeDocs(trait.getValue())); writer.write("$L $L = $L", label, symbol.getName(), entry.getValue().expectTrait(EnumValueTrait.class).expectIntValue()); } }).write(""); // TODO(smithy): type aliases don't allow defining methods on base types (e.g. int32). // Uncomment generating the Value() method when the type alias is migrated to type definition. /* writer.writeDocs(String.format("Values returns all known values for %s. Note that this can be expanded in the " + "future, and so it is only as up to date as the client.%n%nThe ordering of this slice is not " + "guaranteed to be stable across updates.", symbol.getName())); writer.openBlock("func ($L) Values() []$L {", "}", symbol.getName(), symbol.getName(), () -> { writer.openBlock("return []$L{", "}", symbol.getName(), () -> { for (Map.Entry entry : shape.getEnumValues().entrySet()) { writer.write("$L,", entry.getValue()); } }); }); */ } } ManifestWriter.java000066400000000000000000000144051463735525100353120ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import java.util.stream.Collectors; import software.amazon.smithy.build.FileManifest; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.BooleanNode; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.node.StringNode; import software.amazon.smithy.model.traits.UnstableTrait; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyInternalApi; /** * Generates a manifest description of the generated code, minimum go version, * and minimum dependencies required. */ @SmithyInternalApi public final class ManifestWriter { private static final Logger LOGGER = Logger.getLogger(ManifestWriter.class.getName()); private static final String GENERATED_JSON = "generated.json"; private final String moduleName; private final FileManifest fileManifest; private final GoModuleInfo goModuleInfo; private final boolean isUnstable; private ManifestWriter(Builder builder) { moduleName = SmithyBuilder.requiredState("moduleName", builder.moduleName); fileManifest = SmithyBuilder.requiredState("fileManifest", builder.fileManifest); goModuleInfo = SmithyBuilder.requiredState("goModuleInfo", builder.goModuleInfo); isUnstable = builder.isUnstable; } /** * Write the manifest description of the Smithy model based generated source code. * * @param settings the go settings * @param model the smithy model * @param fileManifest the file manifest * @param goModuleInfo the go module info */ public static void writeManifest( GoSettings settings, Model model, FileManifest fileManifest, GoModuleInfo goModuleInfo ) { builder() .moduleName(settings.getModuleName()) .fileManifest(fileManifest) .goModuleInfo(goModuleInfo) .isUnstable(settings.getService(model).getTrait(UnstableTrait.class).isPresent()) .build() .writeManifest(); } /** * Write the manifest description of the generated code. */ public void writeManifest() { Path manifestFile = fileManifest.getBaseDir().resolve(GENERATED_JSON); if (Files.exists(manifestFile)) { try { Files.delete(manifestFile); } catch (IOException e) { throw new CodegenException("Failed to delete existing " + GENERATED_JSON + " file", e); } } fileManifest.addFile(manifestFile); LOGGER.fine("Creating manifest at path " + manifestFile.toString()); Node generatedJson = buildManifestFile(); fileManifest.writeFile(manifestFile.toString(), Node.prettyPrintJson(generatedJson) + "\n"); } private Node buildManifestFile() { Map dependencyNodes = gatherDependencyNodes(goModuleInfo.getMinimumNonStdLibDependencies()); Collection generatedFiles = gatherGeneratedFiles(fileManifest); return ObjectNode.objectNode(Map.of( StringNode.from("module"), StringNode.from(moduleName), StringNode.from("go"), StringNode.from(goModuleInfo.getGoDirective()), StringNode.from("dependencies"), ObjectNode.objectNode(dependencyNodes), StringNode.from("files"), ArrayNode.fromStrings(generatedFiles), StringNode.from("unstable"), BooleanNode.from(isUnstable) )).withDeepSortedKeys(); } private Map gatherDependencyNodes(Map dependencies) { Map dependencyNodes = new HashMap<>(); for (Map.Entry entry : dependencies.entrySet()) { dependencyNodes.put(StringNode.from(entry.getKey()), StringNode.from(entry.getValue())); } return dependencyNodes; } private static Collection gatherGeneratedFiles(FileManifest fileManifest) { Collection generatedFiles = new ArrayList<>(); Path baseDir = fileManifest.getBaseDir(); for (Path filePath : fileManifest.getFiles()) { generatedFiles.add(baseDir.relativize(filePath).toString()); } generatedFiles = generatedFiles.stream().sorted().collect(Collectors.toList()); return generatedFiles; } public static Builder builder() { return new Builder(); } public static class Builder implements SmithyBuilder { private String moduleName; private FileManifest fileManifest; private GoModuleInfo goModuleInfo; private boolean isUnstable; public Builder moduleName(String moduleName) { this.moduleName = moduleName; return this; } public Builder fileManifest(FileManifest fileManifest) { this.fileManifest = fileManifest; return this; } public Builder goModuleInfo(GoModuleInfo goModuleInfo) { this.goModuleInfo = goModuleInfo; return this; } public Builder isUnstable(boolean isUnstable) { this.isUnstable = isUnstable; return this; } @Override public ManifestWriter build() { return new ManifestWriter(this); } } } MiddlewareIdentifier.java000066400000000000000000000106501463735525100364250ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ /* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.Objects; import java.util.Optional; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.utils.SmithyBuilder; /** * A String or Go Symbol of type string used for middleware to identify themselves by. */ public final class MiddlewareIdentifier { private final String string; private final Symbol symbol; private MiddlewareIdentifier(Builder builder) { if ((Objects.isNull(builder.string) && Objects.isNull(builder.symbol))) { throw new IllegalStateException("string or symbol must be provided"); } if ((!Objects.isNull(builder.string) && !Objects.isNull(builder.symbol))) { throw new IllegalStateException("either string or symbol must be provided, not both"); } string = builder.string; symbol = builder.symbol; } public Optional getString() { return Optional.ofNullable(string); } public Optional getSymbol() { return Optional.ofNullable(symbol); } public void writeInline(GoWriter writer) { if (getSymbol().isPresent()) { writer.writeInline("$T", getSymbol().get()); } else if (getString().isPresent()) { writer.writeInline("$S", getString().get()); } else { throw new CodegenException("unsupported identifier state"); } } public static MiddlewareIdentifier symbol(Symbol symbol) { return builder().symbol(symbol).build(); } public static MiddlewareIdentifier string(String string) { return builder().name(string).build(); } @Override public String toString() { if (symbol != null) { return symbol.toString(); } else if (string != null) { return string; } else { throw new CodegenException("unexpected identifier state"); } } public static Builder builder() { return new Builder(); } @Override public int hashCode() { return Objects.hash(string, symbol); } @Override public boolean equals(Object o) { if (this == o) { return true; } else if (!(o instanceof MiddlewareIdentifier)) { return false; } MiddlewareIdentifier identifier = (MiddlewareIdentifier) o; return Objects.equals(string, identifier.string) && ((symbol == identifier.symbol) || (symbol != null && identifier.symbol != null && symbol.getNamespace().equals(identifier.symbol.getNamespace()) && symbol.getName().equals(identifier.symbol.getName()))); } /** * A builder for {@link MiddlewareIdentifier}. */ public static class Builder implements SmithyBuilder { private String string; private Symbol symbol; public Builder name(String name) { this.string = name; return this; } public Builder symbol(Symbol symbol) { this.symbol = symbol; return this; } @Override public MiddlewareIdentifier build() { return new MiddlewareIdentifier(this); } } } OperationGenerator.java000066400000000000000000000314371463735525100361620ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.stream.Stream; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.endpoints.EndpointParameterOperationBindingsGenerator; import software.amazon.smithy.go.codegen.integration.MiddlewareRegistrar; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.OperationIndex; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.traits.DeprecatedTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; /** * Generates a client operation and associated custom shapes. */ public final class OperationGenerator implements Runnable { private final GoCodegenContext ctx; private final Model model; private final SymbolProvider symbolProvider; private final GoWriter writer; private final ServiceShape service; private final OperationShape operation; private final Symbol operationSymbol; private final ProtocolGenerator protocolGenerator; private final List runtimeClientPlugins; OperationGenerator( GoCodegenContext ctx, GoWriter writer, OperationShape operation, ProtocolGenerator protocolGenerator, List runtimeClientPlugins ) { this.ctx = ctx; this.model = ctx.model(); this.symbolProvider = ctx.symbolProvider(); this.writer = writer; this.service = ctx.settings().getService(ctx.model()); this.operation = operation; this.operationSymbol = ctx.symbolProvider().toSymbol(operation); this.protocolGenerator = protocolGenerator; this.runtimeClientPlugins = runtimeClientPlugins; } @Override public void run() { OperationIndex operationIndex = OperationIndex.of(model); Symbol serviceSymbol = symbolProvider.toSymbol(service); if (!operationIndex.getInput(operation).isPresent()) { // Theoretically this shouldn't ever get hit since we automatically insert synthetic inputs / outputs. throw new CodegenException( "Operations are required to have input shapes in order to allow for future evolution."); } StructureShape inputShape = operationIndex.getInput(operation).get(); Symbol inputSymbol = symbolProvider.toSymbol(inputShape); if (!operationIndex.getOutput(operation).isPresent()) { throw new CodegenException( "Operations are required to have output shapes in order to allow for future evolution."); } StructureShape outputShape = operationIndex.getOutput(operation).get(); Symbol outputSymbol = symbolProvider.toSymbol(outputShape); // Generate operation method final boolean hasDocs = writer.writeShapeDocs(operation); operation.getTrait(DeprecatedTrait.class) .ifPresent(trait -> { if (hasDocs) { writer.writeDocs(""); } final String defaultMessage = "This operation has been deprecated."; writer.writeDocs("Deprecated: " + trait.getMessage().map(s -> { if (s.length() == 0) { return defaultMessage; } return s; }).orElse(defaultMessage)); }); Symbol contextSymbol = SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT).build(); writer.openBlock("func (c $P) $T(ctx $T, params $P, optFns ...func(*Options)) ($P, error) {", "}", serviceSymbol, operationSymbol, contextSymbol, inputSymbol, outputSymbol, () -> { writer.write("if params == nil { params = &$T{} }", inputSymbol); writer.write(""); writer.write("result, metadata, err := c.invokeOperation(ctx, $S, params, optFns, c.$L)", operationSymbol.getName(), getAddOperationMiddlewareFuncName(operationSymbol)); writer.write("if err != nil { return nil, err }"); writer.write(""); writer.write("out := result.($P)", outputSymbol); writer.write("out.ResultMetadata = metadata"); writer.write("return out, nil"); }).write(""); // Write out the input and output structures. These are written out here to prevent naming conflicts with other // shapes in the model. new StructureGenerator(model, symbolProvider, writer, service, inputShape, inputSymbol, protocolGenerator) .renderStructure(() -> { }, true); var rulesTrait = service.getTrait(EndpointRuleSetTrait.class); if (rulesTrait.isPresent()) { writer.write(new EndpointParameterOperationBindingsGenerator(ctx, operation, inputShape) .generate()); } // The output structure gets a metadata member added. Symbol metadataSymbol = SymbolUtils.createValueSymbolBuilder("Metadata", SmithyGoDependency.SMITHY_MIDDLEWARE) .build(); boolean hasEventStream = Stream.concat(inputShape.members().stream(), outputShape.members().stream()) .anyMatch(memberShape -> StreamingTrait.isEventStream(model, memberShape)); new StructureGenerator(model, symbolProvider, writer, service, outputShape, outputSymbol, protocolGenerator) .renderStructure(() -> { if (outputShape.getMemberNames().size() != 0) { writer.write(""); } if (hasEventStream) { writer.write("eventStream $P", EventStreamGenerator.getEventStreamOperationStructureSymbol(service, operation)) .write(""); } writer.writeDocs("Metadata pertaining to the operation's result."); writer.write("ResultMetadata $T", metadataSymbol); }); if (hasEventStream) { writer.write(""" // GetStream returns the type to interact with the event stream. func (o $P) GetStream() $P { return o.eventStream } """, outputSymbol, EventStreamGenerator.getEventStreamOperationStructureSymbol( service, operation)); } // Generate operation protocol middleware helper function generateAddOperationMiddleware(); } /** * Adds middleware to the operation middleware stack. */ private void generateAddOperationMiddleware() { Symbol stackSymbol = SymbolUtils.createPointableSymbolBuilder("Stack", SmithyGoDependency.SMITHY_MIDDLEWARE) .build(); writer.openBlock("func (c *Client) $L(stack $P, options Options) (err error) {", "}", getAddOperationMiddlewareFuncName(operationSymbol), stackSymbol, () -> { generateOperationProtocolMiddlewareAdders(); // Populate middleware's from runtime client plugins runtimeClientPlugins.forEach(runtimeClientPlugin -> { if (!runtimeClientPlugin.matchesService(model, service) && !runtimeClientPlugin.matchesOperation(model, service, operation)) { return; } if (!runtimeClientPlugin.registerMiddleware().isPresent()) { return; } MiddlewareRegistrar middlewareRegistrar = runtimeClientPlugin.registerMiddleware().get(); Collection functionArguments = middlewareRegistrar.getFunctionArguments(); if (middlewareRegistrar.getInlineRegisterMiddlewareStatement() != null) { String registerStatement = String.format("if err = stack.%s", middlewareRegistrar.getInlineRegisterMiddlewareStatement()); writer.writeInline(registerStatement); writer.writeInline("$T(", middlewareRegistrar.getResolvedFunction()); if (functionArguments != null) { List args = new ArrayList<>(functionArguments); for (Symbol arg : args) { writer.writeInline("$P, ", arg); } } writer.writeInline(")"); writer.write(", $T); err != nil {\nreturn err\n}", middlewareRegistrar.getInlineRegisterMiddlewarePosition()); } else { writer.writeInline("if err = $T(stack", middlewareRegistrar.getResolvedFunction()); if (functionArguments != null) { List args = new ArrayList<>(functionArguments); for (Symbol arg : args) { writer.writeInline(", $P", arg); } } writer.write("); err != nil {\nreturn err\n}"); } }); writer.write("return nil"); }); } /** * Generate operation protocol middleware helper. */ private void generateOperationProtocolMiddlewareAdders() { if (protocolGenerator == null) { return; } writer.addUseImports(SmithyGoDependency.SMITHY_MIDDLEWARE); // persist operation input to context for internal build/finalize middleware access writer.write(""" if err := stack.Serialize.Add(&setOperationInputMiddleware{}, middleware.After); err != nil { return err }"""); // Add request serializer middleware String serializerMiddlewareName = ProtocolGenerator.getSerializeMiddlewareName( operation.getId(), service, protocolGenerator.getProtocolName()); writer.write("err = stack.Serialize.Add(&$L{}, middleware.After)", serializerMiddlewareName); writer.write("if err != nil { return err }"); // Adds response deserializer middleware String deserializerMiddlewareName = ProtocolGenerator.getDeserializeMiddlewareName( operation.getId(), service, protocolGenerator.getProtocolName()); writer.write("err = stack.Deserialize.Add(&$L{}, middleware.After)", deserializerMiddlewareName); writer.write("if err != nil { return err }"); // FUTURE: retry middleware should be at the front of finalize, right now it's added by the SDK writer.write(""" if err := addProtocolFinalizerMiddlewares(stack, options, $S); err != nil { return $T("add protocol finalizers: %v", err) }""", operationSymbol.getName(), GoStdlibTypes.Fmt.Errorf); writer.write(""); } /** * Returns the name of the operation's middleware mutator function, that adds all middleware for the operation to * the stack. * * @param operation symbol for operation * @return name of function */ public static String getAddOperationMiddlewareFuncName(Symbol operation) { return String.format("addOperation%sMiddlewares", operation.getName()); } } ProtocolDocumentGenerator.java000066400000000000000000000525521463735525100375230ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.function.Consumer; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator.GenerationContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.selector.Selector; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.utils.IoUtils; /** * Generates the service's internal and external document Go packages. The document packages contain the service * specific document Interface definition, protocol specific document marshaler and unmarshaller implementations for * that interface, and constructors for creating service document types. */ public final class ProtocolDocumentGenerator { public static final String DOCUMENT_INTERFACE_NAME = "Interface"; public static final String NO_DOCUMENT_SERDE_TYPE_NAME = "noSmithyDocumentSerde"; public static final String NEW_LAZY_DOCUMENT = "NewLazyDocument"; public static final String INTERNAL_NEW_DOCUMENT_MARSHALER_FUNC = "NewDocumentMarshaler"; public static final String INTERNAL_NEW_DOCUMENT_UNMARSHALER_FUNC = "NewDocumentUnmarshaler"; public static final String INTERNAL_IS_DOCUMENT_INTERFACE = "IsInterface"; public static final String UNMARSHAL_SMITHY_DOCUMENT_METHOD = "UnmarshalSmithyDocument"; public static final String MARSHAL_SMITHY_DOCUMENT_METHOD = "MarshalSmithyDocument"; private static final String SERVICE_SMITHY_DOCUMENT_INTERFACE = "smithyDocument"; private static final String IS_SMITHY_DOCUMENT_METHOD = "isSmithyDocument"; private final GoSettings settings; private final GoDelegator delegator; private final Model model; private final boolean hasDocumentShapes; public ProtocolDocumentGenerator( GoSettings settings, Model model, GoDelegator delegator ) { this.settings = settings; this.model = model; this.delegator = delegator; ShapeId serviceId = settings.getService().toShapeId(); this.hasDocumentShapes = Selector.parse(String.format("[service = %s] ~> document", serviceId)) .matches(model).findAny() .isPresent(); } /** * Generates any required client types or functions to support protocol document types. */ public void generateDocumentSupport() { generateNoSerdeType(); generateInternalDocumentInterface(); generateDocumentPackage(); } /** * Generates the publicly accessible service document package. This package contains a type alias definition * for document interface, as well as a constructor function for creating a document marshaller. *

* This package is not generated if the service does not have any document shapes in the model. * *

{@code
     * // /document
     * package document
     *
     * import (
     *      internaldocument "/internal/document"
     * )
     *
     * type Interface = internaldocument.Interface
     *
     * func NewLazyDocument(v interface{}) Interface {
     *      return internaldocument.NewDocumentMarshaler(v)
     * }
     * }
*/ private void generateDocumentPackage() { if (!this.hasDocumentShapes) { return; } writeDocumentPackage("doc.go", writer -> { String documentTemplate = IoUtils.readUtf8Resource(getClass(), "document_doc.go.template"); writer.writeRawPackageDocs(documentTemplate); }); writeDocumentPackage("document.go", writer -> { writer.writeDocs(String.format("%s defines a document which is a protocol-agnostic type which supports a " + "JSON-like data-model. You can use this type to send UTF-8 strings, arbitrary precision " + "numbers, booleans, nulls, a list of these values, and a map of UTF-8 strings to these " + "values.", DOCUMENT_INTERFACE_NAME)); writer.writeDocs(""); writer.writeDocs(String.format("You create a document type using the %s function and passing it the Go " + "type to marshal. When receiving a document in an API response, you use the " + "document's UnmarshalSmithyDocument function to decode the response to your desired Go " + "type. Unless documented specifically generated structure types in client packages or " + "client types packages are not supported at this time. Such types embed a " + "noSmithyDocumentSerde and will cause an error to be returned when attempting to send an " + "API request.", NEW_LAZY_DOCUMENT)); writer.writeDocs(""); writer.writeDocs("For more information see the accompanying package documentation and linked references."); writer.write("type $L = $T", DOCUMENT_INTERFACE_NAME, getInternalDocumentSymbol(DOCUMENT_INTERFACE_NAME)) .write(""); writer.writeDocs(String.format("You create document type using the %s function and passing it the Go " + "type to be marshaled and sent to the service. The document marshaler supports semantics similar " + "to the encoding/json Go standard library.", NEW_LAZY_DOCUMENT)); writer.writeDocs(""); writer.writeDocs("For more information see the accompanying package documentation and linked references."); writer.openBlock("func $L(v interface{}) $T {", "}", NEW_LAZY_DOCUMENT, getDocumentSymbol(DOCUMENT_INTERFACE_NAME), () -> { writer.write("return $T(v)", getInternalDocumentSymbol(INTERNAL_NEW_DOCUMENT_MARSHALER_FUNC)); }) .write(""); }); } /** * Generates an unexported type alias for the {@code github.com/aws/smithy-go/document#NoSerde} type in both the * service and types package. This allows for this type to be used as an embedded member in structures to * prevent usage of generated Smithy structure shapes as document types. Additionally, since the member is * unexported this prevents the need de-conflict naming collisions. *

* These type aliases are always generated regardless of whether there are document shapes present in the model * or not. * *

{@code
     * package types
     *
     * type noSmithyDocumentSerde = smithydocument.NoSerde
     *
     * type ExampleStructureShape struct {
     *      FieldOne *string
     *
     *      noSmithyDocumentSerde
     * }
     *
     * }
*/ private void generateNoSerdeType() { Symbol noSerde = SymbolUtils.createValueSymbolBuilder("NoSerde", SmithyGoDependency.SMITHY_DOCUMENT).build(); delegator.useShapeWriter(settings.getService(model), writer -> { writer.write("type $L = $T", NO_DOCUMENT_SERDE_TYPE_NAME, noSerde); }); delegator.useFileWriter("./types/types.go", settings.getModuleName() + "/types", writer -> { writer.write("type $L = $T", NO_DOCUMENT_SERDE_TYPE_NAME, noSerde); }); } /** * Generates the document interface definition in the internal document package. * *
{@code
     * import smithydocument "github.com/aws/smithy-go/document"
     *
     * type smithyDocument interface {
     *      isSmithyDocument()
     * }
     *
     * type Interface interface {
     *      smithydocument.Marshaler
     *      smithydocument.Unmarshaler
     *      smithyDocument
     * }
     * }
*/ private void generateInternalDocumentInterface() { if (!this.hasDocumentShapes) { return; } Symbol serviceSmithyDocumentInterface = getInternalDocumentSymbol(SERVICE_SMITHY_DOCUMENT_INTERFACE); Symbol internalDocumentInterface = getInternalDocumentSymbol(DOCUMENT_INTERFACE_NAME); Symbol smithyDocumentMarshaler = SymbolUtils.createValueSymbolBuilder("Marshaler", SmithyGoDependency.SMITHY_DOCUMENT).build(); Symbol smithyDocumentUnmarshaler = SymbolUtils.createValueSymbolBuilder("Unmarshaler", SmithyGoDependency.SMITHY_DOCUMENT).build(); writeInternalDocumentPackage("document.go", writer -> { writer.writeDocs(String.format("%s is an interface which is used to bind" + " a document type to its service client.", serviceSmithyDocumentInterface)); writer.openBlock("type $T interface {", "}", serviceSmithyDocumentInterface, () -> writer.write("$L()", IS_SMITHY_DOCUMENT_METHOD)) .write(""); writer.writeDocs(String.format("%s is a JSON-like data model type that is protocol agnostic and is used" + "to send open-content to a service.", internalDocumentInterface)); writer.openBlock("type $T interface {", "}", internalDocumentInterface, () -> { writer.write("$T", serviceSmithyDocumentInterface); writer.write("$T", smithyDocumentMarshaler); writer.write("$T", smithyDocumentUnmarshaler); }).write(""); }); writeInternalDocumentPackage("document_test.go", writer -> { writer.write("var _ $T = ($P)(nil)", serviceSmithyDocumentInterface, internalDocumentInterface); writer.write("var _ $T = ($P)(nil)", smithyDocumentMarshaler, internalDocumentInterface); writer.write("var _ $T = ($P)(nil)", smithyDocumentUnmarshaler, internalDocumentInterface); writer.write(""); }); } /** * Generates the internal document Go package for the service client. Delegates the logic for document marshaling * and unmarshalling types to the provided protocol generator using the given context. *

* Generate a document marshaler type for marshaling documents to the service's protocol document format. * *

{@code
     * type documentMarshaler struct {
     *     value interface{}
     * }
     *
     * func NewDocumentMarshaler(v interface{}) Interface {
     *     // default or protocol implementation
     * }
     *
     * func (m *documentMarshaler) UnmarshalSmithyDocument(v interface{}) error {
     *     // implemented by protocol generator
     * }
     *
     * func (m *documentUnmarshaler) MarshalSmithyDocument() ([]byte, error) {
     *     // implemented by protocol generator
     * }
     * }
*

* Generate a document marshaler type for unmarshalling documents from the service's protocol response to a Go * type. * *

{@code
     * type documentUnmarshaler struct {
     *     value interface{}
     * }
     * func NewDocumentUnmarshaler(v interface{}) Interface {
     *     // default or protocol implementation
     * }
     *
     * func (m *documentUnmarshaler) UnmarshalSmithyDocument(v interface{}) error {
     *     // implemented by protocol generator
     * }
     *
     * func (m *documentUnmarshaler) MarshalSmithyDocument() ([]byte, error) {
     *     // implemented by protocol generator
     * }
     * }
*

* Generate {@code IsInterface} function which is used to assert whether a given document type * is a valid service protocol document type implementation. * *

{@code
     * func IsInterface(v Interface) bool {
     *     // implementation
     * }
     * }
* * @param protocolGenerator the protocol generator. * @param context the protocol generator context. */ public void generateInternalDocumentTypes(ProtocolGenerator protocolGenerator, GenerationContext context) { if (!this.hasDocumentShapes) { return; } writeInternalDocumentPackage("document.go", writer -> { Symbol marshalerSymbol = getInternalDocumentSymbol("documentMarshaler", true); Symbol unmarshalerSymbol = getInternalDocumentSymbol("documentUnmarshaler", true); Symbol isDocumentInterface = getInternalDocumentSymbol(INTERNAL_IS_DOCUMENT_INTERFACE); writeInternalDocumentImplementation( writer, marshalerSymbol, () -> { protocolGenerator.generateProtocolDocumentMarshalerUnmarshalDocument(context.toBuilder() .writer(writer) .build()); }, () -> { protocolGenerator.generateProtocolDocumentMarshalerMarshalDocument(context.toBuilder() .writer(writer) .build()); }); writeInternalDocumentImplementation(writer, unmarshalerSymbol, () -> { protocolGenerator.generateProtocolDocumentUnmarshalerUnmarshalDocument(context.toBuilder() .writer(writer) .build()); }, () -> { protocolGenerator.generateProtocolDocumentUnmarshalerMarshalDocument(context.toBuilder() .writer(writer) .build()); }); Symbol documentInterfaceSymbol = getInternalDocumentSymbol(DOCUMENT_INTERFACE_NAME); writer.writeDocs(String.format("%s creates a new document marshaler for the given input type", INTERNAL_NEW_DOCUMENT_MARSHALER_FUNC)); writer.openBlock("func $L(v interface{}) $T {", "}", INTERNAL_NEW_DOCUMENT_MARSHALER_FUNC, documentInterfaceSymbol, () -> { protocolGenerator.generateNewDocumentMarshaler(context.toBuilder() .writer(writer) .build(), marshalerSymbol); }).write(""); writer.writeDocs(String.format("%s creates a new document unmarshaler for the given service response", INTERNAL_NEW_DOCUMENT_UNMARSHALER_FUNC)); writer.openBlock("func $L(v interface{}) $T {", "}", INTERNAL_NEW_DOCUMENT_UNMARSHALER_FUNC, documentInterfaceSymbol, () -> { protocolGenerator.generateNewDocumentUnmarshaler(context.toBuilder() .writer(writer) .build(), unmarshalerSymbol); }).write(""); writer.writeDocs(String.format("%s returns whether the given Interface implementation is" + " a valid client implementation", isDocumentInterface)); writer.openBlock("func $T(v Interface) (ok bool) {", "}", isDocumentInterface, () -> { writer.openBlock("defer func() {", "}()", () -> { writer.openBlock("if err := recover(); err != nil {", "}", () -> writer.write("ok = false")); }); writer.write("v.$L()", IS_SMITHY_DOCUMENT_METHOD); writer.write("return true"); }).write(""); }); } private void writeInternalDocumentImplementation( GoWriter writer, Symbol typeSymbol, Runnable unmarshalMethodDefinition, Runnable marshalMethodDefinition ) { writer.openBlock("type $T struct {", "}", typeSymbol, () -> { writer.write("value interface{}"); }); writer.write(""); writer.openBlock("func (m $P) $L(v interface{}) error {", "}", typeSymbol, UNMARSHAL_SMITHY_DOCUMENT_METHOD, unmarshalMethodDefinition); writer.write(""); writer.openBlock("func (m $P) $L() ([]byte, error) {", "}", typeSymbol, MARSHAL_SMITHY_DOCUMENT_METHOD, marshalMethodDefinition); writer.write(""); writer.write("func (m $P) $L() {}", typeSymbol, IS_SMITHY_DOCUMENT_METHOD); writer.write(""); writer.write("var _ $T = ($P)(nil)", getInternalDocumentSymbol(DOCUMENT_INTERFACE_NAME, true), typeSymbol); writer.write(""); } private void writeDocumentPackage(String fileName, Consumer writerConsumer) { delegator.useFileWriter(getDocumentFilePath(fileName), getDocumentPackage(), writerConsumer); } private void writeInternalDocumentPackage(String fileName, Consumer writerConsumer) { delegator.useFileWriter(getInternalDocumentFilePath(fileName), getInternalDocumentPackage(), writerConsumer); } private String getInternalDocumentPackage() { return Utilities.getInternalDocumentPackage(settings); } private String getDocumentPackage() { return Utilities.getDocumentPackage(settings); } private String getInternalDocumentFilePath(String fileName) { return "./internal/document/" + fileName; } private String getDocumentFilePath(String fileName) { return "./document/" + fileName; } private Symbol getDocumentSymbol(String typeName) { return getDocumentSymbol(typeName, false); } private Symbol getDocumentSymbol(String typeName, boolean pointable) { return Utilities.getDocumentSymbolBuilder(settings, typeName, pointable).build(); } private Symbol getInternalDocumentSymbol(String typeName) { return getInternalDocumentSymbol(typeName, false); } private Symbol getInternalDocumentSymbol(String typeName, boolean pointable) { return Utilities.getInternalDocumentSymbolBuilder(settings, typeName, pointable).build(); } /** * Collection of helper utility functions for creating references to the service client's internal * and external document package types. */ public static final class Utilities { /** * Create a non-pointable {@link Symbol.Builder} for typeName in the service's document package. * * @param settings the Smithy Go settings. * @param typeName the name of the Go type. * @return the symbol builder. */ public static Symbol.Builder getDocumentSymbolBuilder(GoSettings settings, String typeName) { return getDocumentSymbolBuilder(settings, typeName, false); } /** * Create {@link Symbol.Builder} for typeName in the service's document package. * * @param settings the Smithy Go settings. * @param typeName the name of the Go type. * @param pointable whether typeName is pointable. * @return the symbol builder. */ public static Symbol.Builder getDocumentSymbolBuilder( GoSettings settings, String typeName, boolean pointable ) { return pointable ? SymbolUtils.createPointableSymbolBuilder(typeName, getDocumentPackage(settings)) : SymbolUtils.createValueSymbolBuilder(typeName, getDocumentPackage(settings)); } /** * Create a non-pointable {@link Symbol.Builder} for typeName in the service's internal document package. * * @param settings the Smithy Go settings. * @param typeName the name of the Go type. * @return the symbol builder. */ public static Symbol.Builder getInternalDocumentSymbolBuilder(GoSettings settings, String typeName) { return getInternalDocumentSymbolBuilder(settings, typeName, false); } /** * Create {@link Symbol.Builder} for typeName in the service's internal document package. * * @param settings the Smithy Go settings. * @param typeName the name of the Go type. * @param pointable whether typeName is pointable. * @return the symbol builder. */ public static Symbol.Builder getInternalDocumentSymbolBuilder( GoSettings settings, String typeName, boolean pointable ) { Symbol.Builder builder = pointable ? SymbolUtils.createPointableSymbolBuilder(typeName, getInternalDocumentPackage(settings)) : SymbolUtils.createValueSymbolBuilder(typeName, getInternalDocumentPackage(settings)); builder.putProperty(SymbolUtils.NAMESPACE_ALIAS, "internaldocument"); return builder; } private static String getInternalDocumentPackage(GoSettings settings) { return settings.getModuleName() + "/internal/document"; } private static String getDocumentPackage(GoSettings settings) { return settings.getModuleName() + "/document"; } } } SemanticVersion.java000066400000000000000000000256731463735525100354710ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.Comparator; import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import software.amazon.smithy.utils.SmithyBuilder; /** * A semantic version parser that allows for prefixes to be compatible with Go version tags. */ public final class SemanticVersion { // Regular Expression from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string private static final Pattern SEMVER_PATTERN = Pattern.compile("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)" + "(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" + "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); private final String prefix; private final int major; private final int minor; private final int patch; private final String preRelease; private final String build; private SemanticVersion(Builder builder) { prefix = builder.prefix; major = builder.major; minor = builder.minor; patch = builder.patch; preRelease = builder.preRelease; build = builder.build; } /** * The semantic version prefix present before the major version. * * @return the optional prefix */ public Optional getPrefix() { return Optional.ofNullable(prefix); } /** * The major version number. * * @return the major version */ public int getMajor() { return major; } /** * The minor version number. * * @return the minor version */ public int getMinor() { return minor; } /** * The patch version number. * * @return the patch version */ public int getPatch() { return patch; } public Optional getPreRelease() { return Optional.ofNullable(preRelease); } public Optional getBuild() { return Optional.ofNullable(build); } @Override public String toString() { StringBuilder builder = new StringBuilder(); if (getPrefix().isPresent()) { builder.append(getPrefix().get()); } builder.append(getMajor()); builder.append('.'); builder.append(getMinor()); builder.append('.'); builder.append(getPatch()); if (getPreRelease().isPresent()) { builder.append('-'); builder.append(getPreRelease().get()); } if (getBuild().isPresent()) { builder.append('+'); builder.append(getBuild().get()); } return builder.toString(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SemanticVersion that = (SemanticVersion) o; return getMajor() == that.getMajor() && getMinor() == that.getMinor() && getPatch() == that.getPatch() && getPrefix().equals(that.getPrefix()) && getPreRelease().equals(that.getPreRelease()) && getBuild().equals(that.getBuild()); } @Override public int hashCode() { return Objects.hash(getPrefix(), getMajor(), getMinor(), getPatch(), getPreRelease(), getBuild()); } /** * Parse a semantic version string into a {@link SemanticVersion}. * * @param version the semantic version string * @return the SemanticVersion representing the parsed value */ public static SemanticVersion parseVersion(String version) { char[] parseArr = version.toCharArray(); StringBuilder prefixBuilder = new StringBuilder(); int position = 0; while (position < parseArr.length && !Character.isDigit(parseArr[position])) { prefixBuilder.append(parseArr[position]); position++; } String prefix = null; if (prefixBuilder.length() > 0) { prefix = prefixBuilder.toString(); } Matcher matcher = SEMVER_PATTERN.matcher(version.substring(position)); if (!matcher.matches()) { throw newInvalidSemanticVersion(version); } return builder() .prefix(prefix) .major(Integer.parseInt(matcher.group(1))) .minor(Integer.parseInt(matcher.group(2))) .patch(Integer.parseInt(matcher.group(3))) .preRelease(matcher.group(4)) .build(matcher.group(5)) .build(); } private static IllegalArgumentException newInvalidSemanticVersion(String version) { return new IllegalArgumentException("Invalid semantic version string: " + version); } /** * Get a {@link SemanticVersion} builder. * * @return the builder */ public static Builder builder() { return new Builder(); } /** * Return a builder for this {@link SemanticVersion}. * * @return the builder */ public Builder toBuilder() { return builder() .prefix(this.prefix) .major(this.major) .minor(this.minor) .patch(this.patch) .preRelease(this.preRelease) .build(this.build); } /** * Compare two {@link SemanticVersion}, ignoring prefix strings. To validate that prefix strings match * see the overloaded function signature. * * @param o the {@link SemanticVersion} to be compared. * @return the value {@code 0} if this {@code SemanticVersion} is * equal to the argument {@code SemanticVersion}; a value less than * {@code 0} if this {@code SemanticVersion} is less * than the argument {@code SemanticVersion}; and a value greater * than {@code 0} if this {@code SemanticVersion} is * greater than the argument {@code SemanticVersion}. */ public int compareTo(SemanticVersion o) { return compareTo(o, (o1, o2) -> 0); } /** * Compare two {@link SemanticVersion}, using the prefixComparator for comparing the prefix strings. * * @param o the {@link SemanticVersion} to be compared. * @param prefixComparator the comparator for comparing prefixes * @return the value {@code 0} if this {@code SemanticVersion} is * equal to the argument {@code SemanticVersion}; a value less than * {@code 0} if this {@code SemanticVersion} is less * than the argument {@code SemanticVersion}; and a value greater * than {@code 0} if this {@code SemanticVersion} is * greater than the argument {@code SemanticVersion}. */ public int compareTo( SemanticVersion o, Comparator> prefixComparator ) { int cmp = prefixComparator.compare(getPrefix(), o.getPrefix()); if (cmp != 0) { return cmp; } cmp = Integer.compare(getMajor(), o.getMajor()); if (cmp != 0) { return cmp; } cmp = Integer.compare(getMinor(), o.getMinor()); if (cmp != 0) { return cmp; } cmp = Integer.compare(getPatch(), o.getPatch()); if (cmp != 0) { return cmp; } if (!getPreRelease().isPresent() && !o.getPreRelease().isPresent()) { return 0; } if (!getPreRelease().isPresent()) { return 1; } if (!o.getPreRelease().isPresent()) { return -1; } return comparePreRelease(getPreRelease().get(), o.getPreRelease().get()); } private static int comparePreRelease(String x, String y) { String[] xIdentifiers = x.split("\\."); String[] yIdentifiers = y.split("\\."); int cmp = 0; int xPos = 0; int yPos = 0; while (xPos < xIdentifiers.length && yPos < yIdentifiers.length && cmp == 0) { Optional xInt = parsePositiveInteger(xIdentifiers[xPos]); Optional yInt = parsePositiveInteger(yIdentifiers[yPos]); if (xInt.isPresent() && yInt.isPresent()) { cmp = Integer.compare(xInt.get(), yInt.get()); continue; } if (xInt.isPresent()) { cmp = -1; continue; } if (yInt.isPresent()) { cmp = 1; continue; } cmp = xIdentifiers[xPos].compareTo(yIdentifiers[yPos]); xPos++; yPos++; } if (cmp != 0) { return cmp; } int xRemaining = xIdentifiers.length - 1 - xPos; int yRemaining = yIdentifiers.length - 1 - yPos; if (xRemaining == yRemaining) { return 0; } return (xRemaining < yRemaining) ? -1 : 1; } private static Optional parsePositiveInteger(String value) { try { int i = Integer.parseInt(value); if (i < 0) { return Optional.empty(); } return Optional.of(i); } catch (NumberFormatException e) { return Optional.empty(); } } /** * Builder for {@link SemanticVersion}. */ public static final class Builder implements SmithyBuilder { private String prefix; private int major; private int minor; private int patch; private String preRelease; private String build; private Builder() { } public Builder prefix(String prefix) { this.prefix = prefix; return this; } public Builder major(int major) { this.major = major; return this; } public Builder minor(int minor) { this.minor = minor; return this; } public Builder patch(int patch) { this.patch = patch; return this; } public Builder preRelease(String preRelease) { this.preRelease = preRelease; return this; } public Builder build(String build) { this.build = build; return this; } @Override public SemanticVersion build() { return new SemanticVersion(this); } } } ServiceGenerator.java000066400000000000000000000444521463735525100356230ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import static software.amazon.smithy.go.codegen.GoWriter.autoDocTemplate; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.auth.AuthSchemeResolverGenerator; import software.amazon.smithy.go.codegen.auth.GetIdentityMiddlewareGenerator; import software.amazon.smithy.go.codegen.auth.ResolveAuthSchemeMiddlewareGenerator; import software.amazon.smithy.go.codegen.auth.SignRequestMiddlewareGenerator; import software.amazon.smithy.go.codegen.endpoints.EndpointMiddlewareGenerator; import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; import software.amazon.smithy.go.codegen.integration.ClientMember; import software.amazon.smithy.go.codegen.integration.ClientMemberResolver; import software.amazon.smithy.go.codegen.integration.ConfigFieldResolver; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.ServiceIndex; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.utils.MapUtils; /** * Generates a service client, its constructors, and core supporting logic. */ final class ServiceGenerator implements Runnable { public static final String CONFIG_NAME = "Options"; private final GoSettings settings; private final Model model; private final SymbolProvider symbolProvider; private final GoWriter writer; private final ServiceShape service; private final List integrations; private final List runtimePlugins; private final ApplicationProtocol applicationProtocol; private final Map authSchemes; ServiceGenerator( GoSettings settings, Model model, SymbolProvider symbolProvider, GoWriter writer, ServiceShape service, List integrations, List runtimePlugins, ApplicationProtocol applicationProtocol ) { this.settings = settings; this.model = model; this.symbolProvider = symbolProvider; this.writer = writer; this.service = service; this.integrations = integrations; this.runtimePlugins = runtimePlugins; this.applicationProtocol = applicationProtocol; this.authSchemes = integrations.stream() .flatMap(it -> it.getClientPlugins(model, service).stream()) .flatMap(it -> it.getAuthSchemeDefinitions().entrySet().stream()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override public void run() { writer.write(generate()); writeProtocolResolverImpls(); } private GoWriter.Writable generate() { return GoWriter.ChainWritable.of( generateMetadata(), generateClient(), generateNew(), generateGetOptions(), generateInvokeOperation(), generateInputContextFuncs(), generateAddProtocolFinalizerMiddleware() ).compose(); } private GoWriter.Writable generateMetadata() { var serviceId = settings.getService().toString(); for (var integration : integrations) { serviceId = integration.processServiceId(settings, model, serviceId); } return goTemplate(""" const ServiceID = $S const ServiceAPIVersion = $S """, serviceId, service.getVersion()); } private GoWriter.Writable generateClient() { return goTemplate(""" $W type $T struct { options $L $W } """, generateClientDocs(), symbolProvider.toSymbol(service), CONFIG_NAME, GoWriter.ChainWritable.of( getAllClientMembers().stream() .map(this::generateClientMember) .toList() ).compose()); } private GoWriter.Writable generateClientDocs() { return writer -> writer.writeDocs(String.format( "%s provides the API client to make operations call for %s.", symbolProvider.toSymbol(service).getName(), CodegenUtils.getServiceTitle(service, "the API") )); } private GoWriter.Writable generateClientMember(ClientMember member) { return goTemplate(""" $W $L $P """, member.getDocumentation().isPresent() ? goDocTemplate(member.getDocumentation().get()) : emptyGoTemplate(), member.getName(), member.getType()); } private GoWriter.Writable generateNew() { var plugins = runtimePlugins.stream() .filter(it -> it.matchesService(model, service)) .toList(); var serviceSymbol = symbolProvider.toSymbol(service); var docs = goDocTemplate( "New returns an initialized $name:L based on the functional options. Provide " + "additional functional options to further configure the behavior of the client, such as changing the " + "client's endpoint or adding custom middleware behavior.", MapUtils.of("name", serviceSymbol.getName())); return goTemplate(""" $docs:W func New(options $options:L, optFns ...func(*$options:L)) *$client:L { options = options.Copy() $resolvers:W $protocolResolvers:W for _, fn := range optFns { fn(&options) } $finalizers:W $protocolFinalizers:W client := &$client:L{ options: options, } $withClientFinalizers:W $clientMemberResolvers:W return client } """, MapUtils.of( "docs", docs, "options", CONFIG_NAME, "client", serviceSymbol.getName(), "protocolResolvers", generateProtocolResolvers(), "protocolFinalizers", generateProtocolFinalizers(), "resolvers", GoWriter.ChainWritable.of( getConfigResolvers( ConfigFieldResolver.Location.CLIENT, ConfigFieldResolver.Target.INITIALIZATION ).map(this::generateConfigFieldResolver).toList() ).compose(), "finalizers", GoWriter.ChainWritable.of( getConfigResolvers( ConfigFieldResolver.Location.CLIENT, ConfigFieldResolver.Target.FINALIZATION ).map(this::generateConfigFieldResolver).toList() ).compose(), "withClientFinalizers", GoWriter.ChainWritable.of( getConfigResolvers( ConfigFieldResolver.Location.CLIENT, ConfigFieldResolver.Target.FINALIZATION_WITH_CLIENT ).map(this::generateConfigFieldResolver).toList() ).compose(), "clientMemberResolvers", GoWriter.ChainWritable.of( plugins.stream() .flatMap(it -> it.getClientMemberResolvers().stream()) .map(this::generateClientMemberResolver) .toList() ).compose() )); } private GoWriter.Writable generateGetOptions() { var docs = autoDocTemplate(""" Options returns a copy of the client configuration. Callers SHOULD NOT perform mutations on any inner structures within client config. Config overrides should instead be made on a per-operation basis through functional options."""); return goTemplate(""" $W func (c $P) Options() $L { return c.options.Copy() } """, docs, symbolProvider.toSymbol(service), ClientOptions.NAME); } private GoWriter.Writable generateConfigFieldResolver(ConfigFieldResolver resolver) { return writer -> { writer.writeInline("$T(&options", resolver.getResolver()); if (resolver.isWithOperationName()) { writer.writeInline(", opID"); } if (resolver.isWithClientInput()) { if (resolver.getLocation() == ConfigFieldResolver.Location.CLIENT) { writer.writeInline(", client"); } else { writer.writeInline(", *c"); } } writer.write(")"); }; } private GoWriter.Writable generateClientMemberResolver(ClientMemberResolver resolver) { return goTemplate("$T(client)", resolver.getResolver()); } private List getAllClientMembers() { List clientMembers = new ArrayList<>(); for (RuntimeClientPlugin runtimeClientPlugin : runtimePlugins) { if (!runtimeClientPlugin.matchesService(model, service)) { continue; } clientMembers.addAll(runtimeClientPlugin.getClientMembers()); } return clientMembers.stream() .distinct() .sorted(Comparator.comparing(ClientMember::getName)) .collect(Collectors.toList()); } private GoWriter.Writable generateProtocolResolvers() { ensureSupportedProtocol(); return goTemplate(""" resolveAuthSchemeResolver(&options) """); } private GoWriter.Writable generateProtocolFinalizers() { ensureSupportedProtocol(); return goTemplate(""" resolveAuthSchemes(&options) """); } private void writeProtocolResolverImpls() { ensureSupportedProtocol(); var schemeMappings = GoWriter.ChainWritable.of( ServiceIndex.of(model) .getEffectiveAuthSchemes(service).keySet().stream() .filter(authSchemes::containsKey) .map(authSchemes::get) .map(it -> goTemplate("$W, ", it.generateDefaultAuthScheme())) .toList() ).compose(false); writer.write(""" func resolveAuthSchemeResolver(options *Options) { if options.AuthSchemeResolver == nil { options.AuthSchemeResolver = &$L{} } } func resolveAuthSchemes(options *Options) { if options.AuthSchemes == nil { options.AuthSchemes = []$T{ $W } } } """, AuthSchemeResolverGenerator.DEFAULT_NAME, SmithyGoTypes.Transport.Http.AuthScheme, schemeMappings); } @SuppressWarnings("checkstyle:LineLength") private GoWriter.Writable generateInvokeOperation() { return goTemplate(""" func (c *Client) invokeOperation(ctx $context:T, opID string, params interface{}, optFns []func(*Options), stackFns ...func($stack:P, Options) error) (result interface{}, metadata $metadata:T, err error) { ctx = $clearStackValues:T(ctx) $newStack:W options := c.options.Copy() $resolvers:W for _, fn := range optFns { fn(&options) } $finalizers:W for _, fn := range stackFns { if err := fn(stack, options); err != nil { return nil, metadata, err } } for _, fn := range options.APIOptions { if err := fn(stack); err != nil { return nil, metadata, err } } $newStackHandler:W result, metadata, err = handler.Handle(ctx, params) if err != nil { err = &$operationError:T{ ServiceID: ServiceID, OperationName: opID, Err: err, } } return result, metadata, err } """, MapUtils.of( "context", GoStdlibTypes.Context.Context, "stack", SmithyGoTypes.Middleware.Stack, "metadata", SmithyGoTypes.Middleware.Metadata, "clearStackValues", SmithyGoTypes.Middleware.ClearStackValues, "newStack", generateNewStack(), "newStackHandler", generateNewStackHandler(), "operationError", SmithyGoTypes.Smithy.OperationError, "resolvers", GoWriter.ChainWritable.of( getConfigResolvers( ConfigFieldResolver.Location.OPERATION, ConfigFieldResolver.Target.INITIALIZATION ).map(this::generateConfigFieldResolver).toList() ).compose(), "finalizers", GoWriter.ChainWritable.of( getConfigResolvers( ConfigFieldResolver.Location.OPERATION, ConfigFieldResolver.Target.FINALIZATION ).map(this::generateConfigFieldResolver).toList() ).compose() )); } private GoWriter.Writable generateNewStack() { ensureSupportedProtocol(); return goTemplate("stack := $T(opID, $T)", SmithyGoTypes.Middleware.NewStack, SmithyGoTypes.Transport.Http.NewStackRequest); } private GoWriter.Writable generateNewStackHandler() { ensureSupportedProtocol(); return goTemplate("handler := $T($T(options.HTTPClient), stack)", SmithyGoTypes.Middleware.DecorateHandler, SmithyGoTypes.Transport.Http.NewClientHandler); } private void ensureSupportedProtocol() { if (!applicationProtocol.isHttpProtocol()) { throw new UnsupportedOperationException( "Protocols other than HTTP are not yet implemented: " + applicationProtocol); } } private Stream getConfigResolvers( ConfigFieldResolver.Location location, ConfigFieldResolver.Target target ) { return runtimePlugins.stream() .filter(it -> it.matchesService(model, service)) .flatMap(it -> it.getConfigFieldResolvers().stream()) .filter(it -> it.getLocation() == location && it.getTarget() == target); } private GoWriter.Writable generateInputContextFuncs() { return goTemplate(""" type operationInputKey struct{} func setOperationInput(ctx $1T, input interface{}) $1T { return $2T(ctx, operationInputKey{}, input) } func getOperationInput(ctx $1T) interface{} { return $3T(ctx, operationInputKey{}) } $4W """, GoStdlibTypes.Context.Context, SmithyGoTypes.Middleware.WithStackValue, SmithyGoTypes.Middleware.GetStackValue, new SetOperationInputContextMiddleware().generate()); } private GoWriter.Writable generateAddProtocolFinalizerMiddleware() { ensureSupportedProtocol(); return goTemplate(""" func addProtocolFinalizerMiddlewares(stack $P, options $L, operation string) error { $W return nil } """, SmithyGoTypes.Middleware.Stack, CONFIG_NAME, GoWriter.ChainWritable.of( ResolveAuthSchemeMiddlewareGenerator.generateAddToProtocolFinalizers(), GetIdentityMiddlewareGenerator.generateAddToProtocolFinalizers(), EndpointMiddlewareGenerator.generateAddToProtocolFinalizers(), SignRequestMiddlewareGenerator.generateAddToProtocolFinalizers() ).compose(false)); } } SetOperationInputContextMiddleware.java000066400000000000000000000032241463735525100413430ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import static software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator.createSerializeStepMiddleware; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; /** * Middleware to set the final operation input on the context at the start of the serialize step such that protocol * middlewares in later phases can use it. */ public class SetOperationInputContextMiddleware { public static final String MIDDLEWARE_NAME = "setOperationInputMiddleware"; public static final String MIDDLEWARE_ID = "setOperationInput"; public GoWriter.Writable generate() { return createSerializeStepMiddleware(MIDDLEWARE_NAME, MiddlewareIdentifier.string(MIDDLEWARE_ID)) .asWritable(generateBody(), emptyGoTemplate()); } private GoWriter.Writable generateBody() { return goTemplate(""" ctx = setOperationInput(ctx, in.Parameters) return next.HandleSerialize(ctx, in) """); } } ShapeValueGenerator.java000066400000000000000000000720171463735525100362560ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. * * */ package software.amazon.smithy.go.codegen; import java.math.BigInteger; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.logging.Logger; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.BooleanNode; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.NodeVisitor; import software.amazon.smithy.model.node.NullNode; import software.amazon.smithy.model.node.NumberNode; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.node.StringNode; import software.amazon.smithy.model.shapes.MapShape; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.shapes.SimpleShape; 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.HttpPrefixHeadersTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.model.traits.Trait; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.OptionalUtils; import software.amazon.smithy.utils.SmithyBuilder; /** * Generates a shape type declaration based on the parameters provided. */ public final class ShapeValueGenerator { private static final Logger LOGGER = Logger.getLogger(ShapeValueGenerator.class.getName()); private final GoSettings settings; private final Model model; private final SymbolProvider symbolProvider; private final GoPointableIndex pointableIndex; private final Config config; /** * Initializes a shape value generator. * * @param settings the Smithy Go settings. * @param model the Smithy model references. * @param symbolProvider the symbol provider. */ public ShapeValueGenerator(GoSettings settings, Model model, SymbolProvider symbolProvider) { this(settings, model, symbolProvider, Config.builder().build()); } /** * Initializes a shape value generator. * * @param settings the Smithy Go settings. * @param model the Smithy model references. * @param symbolProvider the symbol provider. * @param config the shape value generator config. */ public ShapeValueGenerator(GoSettings settings, Model model, SymbolProvider symbolProvider, Config config) { this.settings = settings; this.model = model; this.symbolProvider = symbolProvider; this.pointableIndex = GoPointableIndex.of(model); this.config = config; } /** * Writes generation of a shape value type declaration for the given the parameters. * * @param writer writer to write generated code with. * @param shape the shape that will be declared. * @param params parameters to fill the generated shape declaration. */ public void writePointableStructureShapeValueInline(GoWriter writer, StructureShape shape, Node params) { if (params.isNullNode()) { writer.writeInline("nil"); } // Input/output struct top level shapes are special since they are the only shape that can be used directly, // not within the context of a member shape reference. Symbol symbol = symbolProvider.toSymbol(shape); writer.write("&$T{", symbol); params.accept(new ShapeValueNodeVisitor(writer, this, shape, ListUtils.copyOf(shape.getAllTraits().values()), config)); writer.writeInline("}"); } /** * Writes generation of a member shape value type declaration for the given the parameters. * * @param writer writer to write generated code with. * @param member the shape that will be declared. * @param params parameters to fill the generated shape declaration. */ protected void writeMemberValueInline(GoWriter writer, MemberShape member, Node params) { Shape targetShape = model.expectShape(member.getTarget()); // Null params need to be represented as zero values for member, if (params.isNullNode()) { if (pointableIndex.isNillable(member)) { writer.writeInline("nil"); } else if (targetShape.getType() == ShapeType.STRING && targetShape.hasTrait(EnumTrait.class)) { Symbol enumSymbol = symbolProvider.toSymbol(targetShape); writer.writeInline("$T($S)", enumSymbol, ""); } else { Symbol shapeSymbol = symbolProvider.toSymbol(member); writer.writeInline("func() (v $P) { return v }()", shapeSymbol); } return; } switch (targetShape.getType()) { case STRUCTURE: structDeclShapeValue(writer, member, params); break; case SET: case LIST: listDeclShapeValue(writer, member, params); break; case MAP: mapDeclShapeValue(writer, member, params); break; case UNION: unionDeclShapeValue(writer, member, params.expectObjectNode()); break; case DOCUMENT: documentDeclShapeValue(writer, member, params); break; default: writeScalarPointerInline(writer, member, params); } } private void documentDeclShapeValue(GoWriter writer, MemberShape member, Node params) { Symbol newMarshaler = ProtocolDocumentGenerator.Utilities.getDocumentSymbolBuilder(settings, ProtocolDocumentGenerator.NEW_LAZY_DOCUMENT).build(); writer.writeInline("$T(", newMarshaler); params.accept(new DocumentValueNodeVisitor(writer)); writer.writeInline(")"); } /** * Writes the declaration for a Go structure. Delegates to the runner for member fields within the structure. * * @param writer writer to write generated code with. * @param member the structure shape * @param params parameters to fill the generated shape declaration. */ protected void structDeclShapeValue(GoWriter writer, MemberShape member, Node params) { Symbol symbol = symbolProvider.toSymbol(member); String addr = CodegenUtils.asAddressIfAddressable(model, pointableIndex, member, ""); writer.write("$L$T{", addr, symbol); params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget()), ListUtils.copyOf(member.getAllTraits().values()), config)); writer.writeInline("}"); } /** * Writes the declaration for a Go union. * * @param writer writer to write generated code with. * @param member the union shape. * @param params the params. */ protected void unionDeclShapeValue(GoWriter writer, MemberShape member, ObjectNode params) { UnionShape targetShape = (UnionShape) model.expectShape(member.getTarget()); for (Map.Entry entry : params.getMembers().entrySet()) { targetShape.getMember(entry.getKey().toString()).ifPresent((unionMember) -> { Shape unionTarget = model.expectShape(unionMember.getTarget()); // Need to manually create a symbol builder for a union member struct type because the "member" // of a union will return the inner value type not the member not the member type it self. Symbol memberSymbol = SymbolUtils.createPointableSymbolBuilder( symbolProvider.toMemberName(unionMember), symbolProvider.toSymbol(targetShape).getNamespace() ).build(); // Union member types are always pointers writer.writeInline("&$T{Value: ", memberSymbol); if (unionTarget instanceof SimpleShape) { writeScalarValueInline(writer, unionMember, entry.getValue()); } else { writeMemberValueInline(writer, unionMember, entry.getValue()); } writer.writeInline("}"); }); return; } } /** * Writes the declaration for a Go slice. Delegates to the runner for fields within the slice. * * @param writer writer to write generated code with. * @param member the collection shape * @param params parameters to fill the generated shape declaration. */ protected void listDeclShapeValue(GoWriter writer, MemberShape member, Node params) { writer.write("$P{", symbolProvider.toSymbol(member)); params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget()), ListUtils.copyOf(member.getAllTraits().values()), config)); writer.writeInline("}"); } /** * Writes the declaration for a Go map. Delegates to the runner for key/value fields within the map. * * @param writer writer to write generated code with. * @param member the map shape. * @param params parameters to fill the generated shape declaration. */ protected void mapDeclShapeValue(GoWriter writer, MemberShape member, Node params) { writer.write("$P{", symbolProvider.toSymbol(member)); params.accept(new ShapeValueNodeVisitor(writer, this, model.expectShape(member.getTarget()), ListUtils.copyOf(member.getAllTraits().values()), config)); writer.writeInline("}"); } private void writeScalarWrapper( GoWriter writer, MemberShape member, Node params, String funcName, TriConsumer inner ) { if (pointableIndex.isPointable(member)) { writer.addUseImports(SmithyGoDependency.SMITHY_PTR); writer.writeInline("ptr." + funcName + "("); inner.accept(writer, member, params); writer.writeInline(")"); } else { inner.accept(writer, member, params); } } /** * Writes scalar values with pointer value wrapping as needed based on the shape type. * * @param writer writer to write generated code with. * @param member scalar shape. * @param params parameters to fill the generated shape declaration. */ protected void writeScalarPointerInline(GoWriter writer, MemberShape member, Node params) { Shape target = model.expectShape(member.getTarget()); String funcName = ""; switch (target.getType()) { case BOOLEAN: funcName = "Bool"; break; case STRING: funcName = "String"; break; case ENUM: funcName = target.getId().getName(); break; case TIMESTAMP: funcName = "Time"; break; case BYTE: funcName = "Int8"; break; case SHORT: funcName = "Int16"; break; case INTEGER: case INT_ENUM: funcName = "Int32"; break; case LONG: funcName = "Int64"; break; case FLOAT: funcName = "Float32"; break; case DOUBLE: funcName = "Float64"; break; case BLOB: break; case BIG_INTEGER: case BIG_DECIMAL: return; default: throw new CodegenException("unexpected shape type " + target.getType()); } writeScalarWrapper(writer, member, params, funcName, this::writeScalarValueInline); } protected void writeScalarValueInline(GoWriter writer, MemberShape member, Node params) { Shape target = model.expectShape(member.getTarget()); String closing = ""; switch (target.getType()) { case BLOB: // blob streams are io.Readers not byte slices. if (target.hasTrait(StreamingTrait.class)) { writer.addUseImports(SmithyGoDependency.SMITHY_IO); writer.addUseImports(SmithyGoDependency.BYTES); writer.writeInline("smithyio.ReadSeekNopCloser{ReadSeeker: bytes.NewReader([]byte("); closing = "))}"; } else { writer.writeInline("[]byte("); closing = ")"; } break; case STRING: // String streams are io.Readers not strings. if (target.hasTrait(StreamingTrait.class)) { writer.addUseImports(SmithyGoDependency.SMITHY_IO); writer.addUseImports(SmithyGoDependency.STRINGS); writer.writeInline("smithyio.ReadSeekNopCloser{ReadSeeker: strings.NewReader("); closing = ")}"; } else if (target.hasTrait(EnumTrait.class)) { // Enum are not pointers, but string alias values Symbol enumSymbol = symbolProvider.toSymbol(target); writer.writeInline("$T(", enumSymbol); closing = ")"; } break; case ENUM: // Enum are not pointers, but string alias values Symbol enumSymbol = symbolProvider.toSymbol(target); writer.writeInline("$T(", enumSymbol); closing = ")"; break; default: break; } params.accept(new ShapeValueNodeVisitor(writer, this, target, ListUtils.copyOf(member.getAllTraits().values()), config)); writer.writeInline(closing); } /** * Configuration that determines how shapes values are generated. */ public static final class Config { private final boolean normalizeHttpPrefixHeaderKeys; private Config(Builder builder) { normalizeHttpPrefixHeaderKeys = builder.normalizeHttpPrefixHeaderKeys; } public static Builder builder() { return new Builder(); } /** * Returns whether maps with the httpPrefixHeader trait should have their keys normalized. * * @return whether to normalize http prefix header keys */ public boolean isNormalizeHttpPrefixHeaderKeys() { return normalizeHttpPrefixHeaderKeys; } public static final class Builder implements SmithyBuilder { private boolean normalizeHttpPrefixHeaderKeys; public Builder normalizeHttpPrefixHeaderKeys(boolean normalizeHttpPrefixHeaderKeys) { this.normalizeHttpPrefixHeaderKeys = normalizeHttpPrefixHeaderKeys; return this; } @Override public Config build() { return new Config(this); } } } private static final class DocumentValueNodeVisitor implements NodeVisitor { private final GoWriter writer; private DocumentValueNodeVisitor(GoWriter writer) { this.writer = writer; } @Override public Void arrayNode(ArrayNode node) { writer.writeInline("[]interface{}{\n"); for (Node element : node.getElements()) { element.accept(this); writer.writeInline(",\n"); } writer.writeInline("}"); return null; } @Override public Void booleanNode(BooleanNode node) { if (node.getValue()) { writer.writeInline("true"); } else { writer.writeInline("false"); } return null; } @Override public Void nullNode(NullNode node) { writer.writeInline("nil"); return null; } @Override public Void numberNode(NumberNode node) { if (node.isNaturalNumber()) { Number value = node.getValue(); if (value instanceof BigInteger) { writer.addUseImports(SmithyGoDependency.BIG); writer.writeInline("func () *big.Int {\n" + "\ti, ok := (&big.Int{}).SetString($S, 10)\n" + "\tif !ok { panic(\"failed to parse string to integer: \" + $S) }\n" + "\treturn i\n" + "}()", value, value); } else { writer.writeInline("$L", node.getValue()); } } else { Number value = node.getValue(); if (value instanceof Float) { writer.writeInline("float32($L)", value.floatValue(), value); } else if (value instanceof Double) { writer.writeInline("float64($L)", value.doubleValue(), value); } else { writer.addUseImports(SmithyGoDependency.BIG); writer.writeInline("func () *big.Float {\n" + "\tf, ok := (&big.Float{}).SetString($S)\n" + "\tif !ok { panic(\"failed to parse string to float: \" + $S) }\n" + "\treturn f\n" + "}()", value, value); } } return null; } @Override public Void objectNode(ObjectNode node) { writer.writeInline("map[string]interface{}{\n"); node.getMembers().forEach((key, value) -> { writer.writeInline("$S: ", key.getValue()); value.accept(this); writer.writeInline(",\n"); }); writer.writeInline("}"); return null; } @Override public Void stringNode(StringNode node) { writer.writeInline("$S", node.getValue()); return null; } } /** * NodeVisitor to walk shape value declarations with node values. */ private final class ShapeValueNodeVisitor implements NodeVisitor { private final GoWriter writer; private final ShapeValueGenerator valueGen; private final Shape currentShape; private final List traits; private final Config config; /** * Initializes shape value visitor. * * @param writer writer to write generated code with. * @param valueGen shape value generator. * @param shape the shape that visiting is relative to. */ private ShapeValueNodeVisitor(GoWriter writer, ShapeValueGenerator valueGen, Shape shape) { this(writer, valueGen, shape, ListUtils.of()); } /** * Initializes shape value visitor. * * @param writer writer to write generated code with. * @param valueGen shape value generator. * @param shape the shape that visiting is relative to. * @param traits the traits applied to the target shape by a MemberShape. */ private ShapeValueNodeVisitor(GoWriter writer, ShapeValueGenerator valueGen, Shape shape, List traits) { this(writer, valueGen, shape, traits, Config.builder().build()); } /** * Initializes shape value visitor. * * @param writer writer to write generated code with. * @param valueGen shape value generator. * @param shape the shape that visiting is relative to. * @param traits the traits applied to the target shape by a MemberShape. * @param config the shape value generator config. */ private ShapeValueNodeVisitor( GoWriter writer, ShapeValueGenerator valueGen, Shape shape, List traits, Config config ) { this.writer = writer; this.valueGen = valueGen; this.currentShape = shape; this.traits = traits; this.config = config; } /** * When array nodes elements are encountered. * * @param node the node * @return always null */ @Override public Void arrayNode(ArrayNode node) { MemberShape memberShape = CodegenUtils.expectCollectionShape(this.currentShape).getMember(); node.getElements().forEach(element -> { valueGen.writeMemberValueInline(writer, memberShape, element); writer.write(","); }); return null; } /** * When an object node elements are encountered. * * @param node the node * @return always null */ @Override public Void objectNode(ObjectNode node) { node.getMembers().forEach((keyNode, valueNode) -> { MemberShape member; switch (currentShape.getType()) { case STRUCTURE: if (currentShape.asStructureShape().get().getMember(keyNode.getValue()).isPresent()) { member = currentShape.asStructureShape().get().getMember(keyNode.getValue()).get(); } else { throw new CodegenException( "unknown member " + currentShape.getId() + "." + keyNode.getValue()); } String memberName = symbolProvider.toMemberName(member); writer.write("$L: ", memberName); valueGen.writeMemberValueInline(writer, member, valueNode); writer.write(","); break; case MAP: MapShape mapShape = this.currentShape.asMapShape().get(); String keyValue = keyNode.getValue(); if (config.isNormalizeHttpPrefixHeaderKeys()) { keyValue = OptionalUtils.or(getTrait(HttpPrefixHeadersTrait.class), () -> mapShape.getTrait(HttpPrefixHeadersTrait.class)) .map(httpPrefixHeadersTrait -> keyNode.getValue().toLowerCase()) .orElse(keyValue); } writer.write("$S: ", keyValue); valueGen.writeMemberValueInline(writer, mapShape.getValue(), valueNode); writer.write(","); break; default: throw new CodegenException("unexpected shape type " + currentShape.getType()); } }); return null; } /** * When boolean nodes are encountered. * * @param node the node * @return always null */ @Override public Void booleanNode(BooleanNode node) { if (!currentShape.getType().equals(ShapeType.BOOLEAN)) { throw new CodegenException("unexpected shape type " + currentShape + " for boolean value"); } writer.writeInline("$L", node.getValue() ? "true" : "false"); return null; } /** * When null nodes are encountered. * * @param node the node * @return always null */ @Override public Void nullNode(NullNode node) { throw new CodegenException("unexpected null node walked, should not be encountered in walker"); } /** * When number nodes are encountered. * * @param node the node * @return always null */ @Override public Void numberNode(NumberNode node) { switch (currentShape.getType()) { case TIMESTAMP: writer.addUseImports(SmithyGoDependency.SMITHY_TIME); writer.writeInline("smithytime.ParseEpochSeconds($L)", node.getValue()); break; case BYTE: case SHORT: case INTEGER: case INT_ENUM: case LONG: case FLOAT: case DOUBLE: writer.writeInline("$L", node.getValue()); break; case BIG_INTEGER: writeInlineBigIntegerInit(writer, node.getValue()); break; case BIG_DECIMAL: writeInlineBigDecimalInit(writer, node.getValue()); break; default: throw new CodegenException("unexpected shape type " + currentShape + " for string value"); } return null; } /** * When string nodes are encountered. * * @param node the node * @return always null */ @Override public Void stringNode(StringNode node) { switch (currentShape.getType()) { case BLOB: case STRING: writer.writeInline("$S", node.getValue()); break; case ENUM: writer.writeInline("$S", node.getValue()); break; case BIG_INTEGER: writeInlineBigIntegerInit(writer, node.getValue()); break; case BIG_DECIMAL: writeInlineBigDecimalInit(writer, node.getValue()); break; case DOUBLE: writeInlineNonNumericFloat(writer, node.getValue()); break; case FLOAT: writer.writeInline("float32("); writeInlineNonNumericFloat(writer, node.getValue()); writer.writeInline(")"); break; default: throw new CodegenException("unexpected shape type " + currentShape.getType()); } return null; } private void writeInlineBigDecimalInit(GoWriter writer, Object value) { writer.addUseImports(SmithyGoDependency.BIG); writer.writeInline("func() *big.Float {\n" + " i, ok := big.ParseFloat($S, 10, 200, big.ToNearestAway)\n" + " if !ok { panic(\"invalid generated param value, \" + $S) }\n" + " return i" + "}()", value, value); } private void writeInlineBigIntegerInit(GoWriter writer, Object value) { writer.addUseImports(SmithyGoDependency.BIG); writer.writeInline("func() *big.Int {\n" + " i, ok := new(big.Int).SetString($S, 10)\n" + " if !ok { panic(\"invalid generated param value, \" + $S) }\n" + " return i" + "}()", value, value); } private void writeInlineNonNumericFloat(GoWriter writer, String value) { writer.addUseImports(SmithyGoDependency.stdlib("math")); switch (value) { case "NaN": writer.writeInline("math.NaN()"); break; case "Infinity": writer.writeInline("math.Inf(1)"); break; case "-Infinity": writer.writeInline("math.Inf(-1)"); break; default: throw new CodegenException(String.format( "Unexpected string value for `%s`: \"%s\"", currentShape.getId(), value)); } } private Optional getTrait(Class traitClass) { for (Trait trait : traits) { if (traitClass.isInstance(trait)) { return Optional.of((T) trait); } } return Optional.empty(); } } } SmithyGoDependency.java000066400000000000000000000162141463735525100361110ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; /** * A class of constants for dependencies used by this package. */ public final class SmithyGoDependency { // The version in the stdlib dependencies should reflect the minimum Go version. // The values aren't currently used, but they could potentially used to dynamically // set the minimum go version. public static final GoDependency BIG = stdlib("math/big"); public static final GoDependency TIME = stdlib("time"); public static final GoDependency FMT = stdlib("fmt"); public static final GoDependency CONTEXT = stdlib("context"); public static final GoDependency STRCONV = stdlib("strconv"); public static final GoDependency BASE64 = stdlib("encoding/base64"); public static final GoDependency NET = stdlib("net"); public static final GoDependency NET_URL = stdlib("net/url"); public static final GoDependency NET_HTTP = stdlib("net/http"); public static final GoDependency NET_HTTP_TEST = stdlib("net/http/httptest"); public static final GoDependency BYTES = stdlib("bytes"); public static final GoDependency STRINGS = stdlib("strings"); public static final GoDependency JSON = stdlib("encoding/json"); public static final GoDependency IO = stdlib("io"); public static final GoDependency IOUTIL = stdlib("io/ioutil"); public static final GoDependency FS = stdlib("io/fs"); public static final GoDependency CRYPTORAND = stdlib("crypto/rand", "cryptorand"); public static final GoDependency TESTING = stdlib("testing"); public static final GoDependency ERRORS = stdlib("errors"); public static final GoDependency XML = stdlib("encoding/xml"); public static final GoDependency SYNC = stdlib("sync"); public static final GoDependency ATOMIC = stdlib("sync/atomic"); public static final GoDependency PATH = stdlib("path"); public static final GoDependency LOG = stdlib("log"); public static final GoDependency OS = stdlib("os"); public static final GoDependency PATH_FILEPATH = stdlib("path/filepath"); public static final GoDependency REFLECT = stdlib("reflect"); public static final GoDependency SMITHY = smithy(null, "smithy"); public static final GoDependency SMITHY_TRANSPORT = smithy("transport", "smithytransport"); public static final GoDependency SMITHY_HTTP_TRANSPORT = smithy("transport/http", "smithyhttp"); public static final GoDependency SMITHY_MIDDLEWARE = smithy("middleware"); public static final GoDependency SMITHY_PRIVATE_PROTOCOL = smithy("private/protocol", "smithyprivateprotocol"); public static final GoDependency SMITHY_REQUEST_COMPRESSION = smithy("private/requestcompression", "smithyrequestcompression"); public static final GoDependency SMITHY_TIME = smithy("time", "smithytime"); public static final GoDependency SMITHY_HTTP_BINDING = smithy("encoding/httpbinding"); public static final GoDependency SMITHY_JSON = smithy("encoding/json", "smithyjson"); public static final GoDependency SMITHY_XML = smithy("encoding/xml", "smithyxml"); public static final GoDependency SMITHY_CBOR = smithy("encoding/cbor", "smithycbor"); public static final GoDependency SMITHY_IO = smithy("io", "smithyio"); public static final GoDependency SMITHY_LOGGING = smithy("logging"); public static final GoDependency SMITHY_PTR = smithy("ptr"); public static final GoDependency SMITHY_RAND = smithy("rand", "smithyrand"); public static final GoDependency SMITHY_TESTING = smithy("testing", "smithytesting"); public static final GoDependency SMITHY_WAITERS = smithy("waiter", "smithywaiter"); public static final GoDependency SMITHY_DOCUMENT = smithy("document", "smithydocument"); public static final GoDependency SMITHY_DOCUMENT_JSON = smithy("document/json", "smithydocumentjson"); public static final GoDependency SMITHY_DOCUMENT_CBOR = smithy("document/cbor", "smithydocumentcbor"); public static final GoDependency SMITHY_SYNC = smithy("sync", "smithysync"); public static final GoDependency SMITHY_AUTH = smithy("auth", "smithyauth"); public static final GoDependency SMITHY_AUTH_BEARER = smithy("auth/bearer"); public static final GoDependency SMITHY_ENDPOINTS = smithy("endpoints", "smithyendpoints"); public static final GoDependency SMITHY_ENDPOINT_RULESFN = smithy("endpoints/private/rulesfn"); public static final GoDependency GO_JMESPATH = goJmespath(null); public static final GoDependency MATH = stdlib("math"); private static final String SMITHY_SOURCE_PATH = "github.com/aws/smithy-go"; private static final String GO_JMESPATH_SOURCE_PATH = "github.com/jmespath/go-jmespath"; private SmithyGoDependency() { } /** * Get a {@link GoDependency} representing the standard library package import path. * * @param importPath standard library import path * @return the {@link GoDependency} for the package import path */ public static GoDependency stdlib(String importPath) { return GoDependency.standardLibraryDependency(importPath, Versions.GO_STDLIB); } /** * Get a {@link GoDependency} representing the standard library package import path with the given alias. * * @param importPath standard library package import path * @param alias the package alias * @return the {@link GoDependency} for the package import path */ public static GoDependency stdlib(String importPath, String alias) { return GoDependency.standardLibraryDependency(importPath, Versions.GO_STDLIB, alias); } private static GoDependency smithy(String relativePath) { return smithy(relativePath, null); } private static GoDependency smithy(String relativePath, String alias) { return relativePackage(SMITHY_SOURCE_PATH, relativePath, Versions.SMITHY_GO, alias); } private static GoDependency goJmespath(String relativePath) { return relativePackage(GO_JMESPATH_SOURCE_PATH, relativePath, Versions.GO_JMESPATH, "jmespath"); } private static GoDependency relativePackage( String moduleImportPath, String relativePath, String version, String alias ) { String importPath = moduleImportPath; if (relativePath != null) { importPath = importPath + "/" + relativePath; } return GoDependency.moduleDependency(moduleImportPath, importPath, version, alias); } private static final class Versions { private static final String GO_STDLIB = "1.15"; private static final String SMITHY_GO = "v1.4.0"; private static final String GO_JMESPATH = "v0.4.0"; } } SmithyGoTypes.java000066400000000000000000000312651463735525100351420ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import software.amazon.smithy.codegen.core.Symbol; /** * Collection of Symbol constants for types in the smithy-go runtime. */ @SuppressWarnings({"checkstyle:ConstantName", "checkstyle:LineLength"}) public final class SmithyGoTypes { private SmithyGoTypes() { } public static final class Smithy { public static final Symbol Properties = SmithyGoDependency.SMITHY.pointableSymbol("Properties"); public static final Symbol OperationError = SmithyGoDependency.SMITHY.pointableSymbol("OperationError"); public static final Symbol InvalidParamsError = SmithyGoDependency.SMITHY.pointableSymbol("InvalidParamsError"); public static final Symbol SerializationError = SmithyGoDependency.SMITHY.pointableSymbol("SerializationError"); public static final class Document { public static final Symbol NoSerde = SmithyGoDependency.SMITHY_DOCUMENT.pointableSymbol("NoSerde"); } } public static final class Time { public static final Symbol ParseDateTime = SmithyGoDependency.SMITHY_TIME.valueSymbol("ParseDateTime"); public static final Symbol FormatDateTime = SmithyGoDependency.SMITHY_TIME.valueSymbol("FormatDateTime"); } public static final class Rand { public static final Symbol NewUUID = SmithyGoDependency.SMITHY_RAND.valueSymbol("NewUUID"); } public static final class Encoding { public static final class Json { public static final Symbol NewEncoder = SmithyGoDependency.SMITHY_JSON.valueSymbol("NewEncoder"); public static final Symbol Value = SmithyGoDependency.SMITHY_JSON.valueSymbol("Value"); } public static final class Cbor { public static final Symbol Encode = SmithyGoDependency.SMITHY_CBOR.valueSymbol("Encode"); public static final Symbol Decode = SmithyGoDependency.SMITHY_CBOR.valueSymbol("Decode"); public static final Symbol Value = SmithyGoDependency.SMITHY_CBOR.valueSymbol("Value"); public static final Symbol Uint = SmithyGoDependency.SMITHY_CBOR.valueSymbol("Uint"); public static final Symbol NegInt = SmithyGoDependency.SMITHY_CBOR.valueSymbol("NegInt"); public static final Symbol Slice = SmithyGoDependency.SMITHY_CBOR.valueSymbol("Slice"); public static final Symbol String = SmithyGoDependency.SMITHY_CBOR.valueSymbol("String"); public static final Symbol List = SmithyGoDependency.SMITHY_CBOR.valueSymbol("List"); public static final Symbol Map = SmithyGoDependency.SMITHY_CBOR.valueSymbol("Map"); public static final Symbol Tag = SmithyGoDependency.SMITHY_CBOR.pointableSymbol("Tag"); public static final Symbol Bool = SmithyGoDependency.SMITHY_CBOR.valueSymbol("Bool"); public static final Symbol Nil = SmithyGoDependency.SMITHY_CBOR.pointableSymbol("Nil"); public static final Symbol Undefined = SmithyGoDependency.SMITHY_CBOR.pointableSymbol("Undefined"); public static final Symbol Float32 = SmithyGoDependency.SMITHY_CBOR.valueSymbol("Float32"); public static final Symbol Float64 = SmithyGoDependency.SMITHY_CBOR.valueSymbol("Float64"); public static final Symbol EncodeRaw = SmithyGoDependency.SMITHY_CBOR.valueSymbol("EncodeRaw"); public static final Symbol AsInt8 = SmithyGoDependency.SMITHY_CBOR.valueSymbol("AsInt8"); public static final Symbol AsInt16 = SmithyGoDependency.SMITHY_CBOR.valueSymbol("AsInt16"); public static final Symbol AsInt32 = SmithyGoDependency.SMITHY_CBOR.valueSymbol("AsInt32"); public static final Symbol AsInt64 = SmithyGoDependency.SMITHY_CBOR.valueSymbol("AsInt64"); public static final Symbol AsFloat32 = SmithyGoDependency.SMITHY_CBOR.valueSymbol("AsFloat32"); public static final Symbol AsFloat64 = SmithyGoDependency.SMITHY_CBOR.valueSymbol("AsFloat64"); public static final Symbol AsTime = SmithyGoDependency.SMITHY_CBOR.valueSymbol("AsTime"); } } public static final class Document { public static final class Cbor { public static final Symbol NewEncoder = SmithyGoDependency.SMITHY_DOCUMENT_CBOR.valueSymbol("NewEncoder"); } } public static final class Ptr { public static final Symbol String = SmithyGoDependency.SMITHY_PTR.valueSymbol("String"); public static final Symbol Bool = SmithyGoDependency.SMITHY_PTR.valueSymbol("Bool"); public static final Symbol Int8 = SmithyGoDependency.SMITHY_PTR.valueSymbol("Int8"); public static final Symbol Int16 = SmithyGoDependency.SMITHY_PTR.valueSymbol("Int16"); public static final Symbol Int32 = SmithyGoDependency.SMITHY_PTR.valueSymbol("Int32"); public static final Symbol Int64 = SmithyGoDependency.SMITHY_PTR.valueSymbol("Int64"); public static final Symbol Float32 = SmithyGoDependency.SMITHY_PTR.valueSymbol("Float32"); public static final Symbol Float64 = SmithyGoDependency.SMITHY_PTR.valueSymbol("Float64"); public static final Symbol Time = SmithyGoDependency.SMITHY_PTR.valueSymbol("Time"); } public static final class Middleware { public static final Symbol Stack = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("Stack"); public static final Symbol NewStack = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("NewStack"); public static final Symbol Metadata = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("Metadata"); public static final Symbol ClearStackValues = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("ClearStackValues"); public static final Symbol WithStackValue = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("WithStackValue"); public static final Symbol GetStackValue = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("GetStackValue"); public static final Symbol After = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("After"); public static final Symbol Before = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("Before"); public static final Symbol DecorateHandler = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("DecorateHandler"); public static final Symbol InitializeInput = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("InitializeInput"); public static final Symbol InitializeOutput = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("InitializeOutput"); public static final Symbol InitializeHandler = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("InitializeHandler"); public static final Symbol SerializeInput = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("SerializeInput"); public static final Symbol SerializeOutput = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("SerializeOutput"); public static final Symbol SerializeHandler = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("SerializeHandler"); public static final Symbol FinalizeInput = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("FinalizeInput"); public static final Symbol FinalizeOutput = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("FinalizeOutput"); public static final Symbol FinalizeHandler = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("FinalizeHandler"); } public static final class Transport { public static final class Http { public static final Symbol Request = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.pointableSymbol("Request"); public static final Symbol Response = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.pointableSymbol("Response"); public static final Symbol NewStackRequest = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("NewStackRequest"); public static final Symbol NewClientHandler = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("NewClientHandler"); public static final Symbol JoinPath = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("JoinPath"); public static final Symbol GetHostnameImmutable = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("GetHostnameImmutable"); public static final Symbol AuthScheme = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("AuthScheme"); public static final Symbol NewAnonymousScheme = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("NewAnonymousScheme"); public static final Symbol GetSigV4SigningName = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("GetSigV4SigningName"); public static final Symbol GetSigV4SigningRegion = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("GetSigV4SigningRegion"); public static final Symbol GetSigV4ASigningName = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("GetSigV4ASigningName"); public static final Symbol GetSigV4ASigningRegions = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("GetSigV4ASigningRegions"); public static final Symbol SetSigV4SigningName = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("SetSigV4SigningName"); public static final Symbol SetSigV4SigningRegion = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("SetSigV4SigningRegion"); public static final Symbol SetSigV4ASigningName = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("SetSigV4ASigningName"); public static final Symbol SetSigV4ASigningRegions = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("SetSigV4ASigningRegions"); public static final Symbol SetIsUnsignedPayload = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("SetIsUnsignedPayload"); public static final Symbol SetDisableDoubleEncoding = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("SetDisableDoubleEncoding"); } } public static final class Auth { public static final Symbol Option = SmithyGoDependency.SMITHY_AUTH.pointableSymbol("Option"); public static final Symbol IdentityResolver = SmithyGoDependency.SMITHY_AUTH.valueSymbol("IdentityResolver"); public static final Symbol Identity = SmithyGoDependency.SMITHY_AUTH.valueSymbol("Identity"); public static final Symbol AnonymousIdentityResolver = SmithyGoDependency.SMITHY_AUTH.pointableSymbol("AnonymousIdentityResolver"); public static final Symbol GetAuthOptions = SmithyGoDependency.SMITHY_AUTH.valueSymbol("GetAuthOptions"); public static final Symbol SetAuthOptions = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SetAuthOptions"); public static final Symbol SchemeIDAnonymous = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDAnonymous"); public static final Symbol SchemeIDHTTPBasic = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDHTTPBasic"); public static final Symbol SchemeIDHTTPDigest = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDHTTPDigest"); public static final Symbol SchemeIDHTTPBearer = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDHTTPBearer"); public static final Symbol SchemeIDHTTPAPIKey = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDHTTPAPIKey"); public static final Symbol SchemeIDSigV4 = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDSigV4"); public static final Symbol SchemeIDSigV4A = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDSigV4A"); public static final class Bearer { public static final Symbol TokenProvider = SmithyGoDependency.SMITHY_AUTH_BEARER.valueSymbol("TokenProvider"); public static final Symbol Signer = SmithyGoDependency.SMITHY_AUTH_BEARER.valueSymbol("Signer"); public static final Symbol NewSignHTTPSMessage = SmithyGoDependency.SMITHY_AUTH_BEARER.valueSymbol("NewSignHTTPSMessage"); } } public static final class Private { public static final class RequestCompression { public static final Symbol AddRequestCompression = SmithyGoDependency.SMITHY_REQUEST_COMPRESSION.valueSymbol("AddRequestCompression"); public static final Symbol AddCaptureUncompressedRequest = SmithyGoDependency.SMITHY_REQUEST_COMPRESSION.valueSymbol("AddCaptureUncompressedRequestMiddleware"); } } } StructureGenerator.java000066400000000000000000000200121463735525100362050ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.Map; import java.util.Set; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SetUtils; import software.amazon.smithy.utils.SmithyInternalApi; /** * Renders structures. */ @SmithyInternalApi public final class StructureGenerator implements Runnable { private static final Map STANDARD_ERROR_MEMBERS = MapUtils.of( "ErrorCode", "string", "ErrorMessage", "string", "ErrorFault", "string" ); private static final Set ERROR_MEMBER_NAMES = SetUtils.of("ErrorMessage", "Message", "ErrorCodeOverride"); private final Model model; private final SymbolProvider symbolProvider; private final GoWriter writer; private final StructureShape shape; private final Symbol symbol; private final ServiceShape service; private final ProtocolGenerator protocolGenerator; public StructureGenerator( Model model, SymbolProvider symbolProvider, GoWriter writer, ServiceShape service, StructureShape shape, Symbol symbol, ProtocolGenerator protocolGenerator ) { this.model = model; this.symbolProvider = symbolProvider; this.writer = writer; this.service = service; this.shape = shape; this.symbol = symbol; this.protocolGenerator = protocolGenerator; } @Override public void run() { if (!shape.hasTrait(ErrorTrait.class)) { renderStructure(() -> { }); } else { renderErrorStructure(); } } /** * Renders a non-error structure. * * @param runnable A runnable that runs before the structure definition is closed. This can be used to write * additional members. */ public void renderStructure(Runnable runnable) { renderStructure(runnable, false); } /** * Renders a non-error structure. * * @param runnable A runnable that runs before the structure definition is closed. This can be used to write * additional members. * @param isInputStructure A boolean indicating if input variants for member symbols should be used. */ public void renderStructure(Runnable runnable, boolean isInputStructure) { writer.writeShapeDocs(shape); writer.openBlock("type $L struct {", symbol.getName()); CodegenUtils.SortedMembers sortedMembers = new CodegenUtils.SortedMembers(symbolProvider); shape.getAllMembers().values().stream() .filter(memberShape -> !StreamingTrait.isEventStream(model, memberShape)) .sorted(sortedMembers) .forEach((member) -> { writer.write(""); String memberName = symbolProvider.toMemberName(member); writer.writeMemberDocs(model, member); Symbol memberSymbol = symbolProvider.toSymbol(member); if (isInputStructure) { memberSymbol = memberSymbol.getProperty(SymbolUtils.INPUT_VARIANT, Symbol.class) .orElse(memberSymbol); } writer.write("$L $P", memberName, memberSymbol); }); runnable.run(); // At this moment there is no support for the concept of modeled document structure types. // We embed the NoSerde type to prevent usage of the generated structure shapes from being used // as document types themselves, or part of broader document-type structures. This avoids making backwards // incompatible changes if the document type representation changes if it is later annotated as a modeled // document type. This restriction may be relaxed later by removing this constraint. writer.write(""); writer.write("$L", ProtocolDocumentGenerator.NO_DOCUMENT_SERDE_TYPE_NAME); writer.closeBlock("}").write(""); } /** * Renders an error structure and supporting methods. */ private void renderErrorStructure() { Symbol structureSymbol = symbolProvider.toSymbol(shape); writer.addUseImports(SmithyGoDependency.SMITHY); writer.addUseImports(SmithyGoDependency.FMT); ErrorTrait errorTrait = shape.expectTrait(ErrorTrait.class); // Write out a struct to hold the error data. writer.writeShapeDocs(shape); writer.openBlock("type $L struct {", "}", structureSymbol.getName(), () -> { // The message is the only part of the standard APIError interface that isn't known ahead of time. // Message is a pointer mostly for the sake of consistency. writer.write("Message *string").write(""); writer.write("ErrorCodeOverride *string").write(""); for (MemberShape member : shape.getAllMembers().values()) { String memberName = symbolProvider.toMemberName(member); // error messages are represented under Message for consistency if (!ERROR_MEMBER_NAMES.contains(memberName)) { writer.write("$L $P", memberName, symbolProvider.toSymbol(member)); } } writer.write(""); writer.write("$L", ProtocolDocumentGenerator.NO_DOCUMENT_SERDE_TYPE_NAME); }).write(""); // write the Error method to satisfy the standard error interface writer.openBlock("func (e *$L) Error() string {", "}", structureSymbol.getName(), () -> { writer.write("return fmt.Sprintf(\"%s: %s\", e.ErrorCode(), e.ErrorMessage())"); }); // Write out methods to satisfy the APIError interface. All but the message are known ahead of time, // and for those we just encode the information in the method itself. writer.openBlock("func (e *$L) ErrorMessage() string {", "}", structureSymbol.getName(), () -> { writer.openBlock("if e.Message == nil {", "}", () -> { writer.write("return \"\""); }); writer.write("return *e.Message"); }); String errorCode = protocolGenerator == null ? shape.getId().getName(service) : protocolGenerator.getErrorCode(service, shape); writer.openBlock("func (e *$L) ErrorCode() string {", "}", structureSymbol.getName(), () -> { writer.openBlock("if e == nil || e.ErrorCodeOverride == nil {", "}", () -> { writer.write("return $S", errorCode); }); writer.write("return *e.ErrorCodeOverride"); }); String fault = "smithy.FaultUnknown"; if (errorTrait.isClientError()) { fault = "smithy.FaultClient"; } else if (errorTrait.isServerError()) { fault = "smithy.FaultServer"; } writer.write("func (e *$L) ErrorFault() smithy.ErrorFault { return $L }", structureSymbol.getName(), fault); } } SymbolUtils.java000066400000000000000000000204121463735525100346300ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.model.shapes.Shape; /** * Common symbol utility building functions. */ public final class SymbolUtils { public static final String POINTABLE = "pointable"; public static final String NAMESPACE_ALIAS = "namespaceAlias"; public static final String GO_UNIVERSE_TYPE = "universeType"; public static final String GO_SLICE = "goSlice"; public static final String GO_MAP = "goMap"; public static final String GO_ELEMENT_TYPE = "goElementType"; // Used when a given shape must be represented differently on input. public static final String INPUT_VARIANT = "inputVariant"; private SymbolUtils() { } /** * Create a value symbol builder. * * @param typeName the name of the type. * @return the symbol builder type. */ public static Symbol.Builder createValueSymbolBuilder(String typeName) { return Symbol.builder() .putProperty(POINTABLE, false) .name(typeName); } /** * Create a pointable symbol builder. * * @param typeName the name of the type. * @return the symbol builder. */ public static Symbol.Builder createPointableSymbolBuilder(String typeName) { return Symbol.builder() .putProperty(POINTABLE, true) .name(typeName); } /** * Create a value symbol builder. * * @param shape the shape that the type is for. * @param typeName the name of the type. * @return the symbol builder. */ public static Symbol.Builder createValueSymbolBuilder(Shape shape, String typeName) { return createValueSymbolBuilder(typeName).putProperty("shape", shape); } /** * Create a pointable symbol builder. * * @param shape the shape that the type is for. * @param typeName the name of the type. * @return the symbol builder. */ public static Symbol.Builder createPointableSymbolBuilder(Shape shape, String typeName) { return createPointableSymbolBuilder(typeName).putProperty("shape", shape); } /** * Create a pointable symbol builder. * * @param typeName the name of the type. * @param namespace the namespace of the type. * @return the symbol builder. */ public static Symbol.Builder createPointableSymbolBuilder(String typeName, String namespace) { return createPointableSymbolBuilder(typeName).namespace(namespace, "."); } /** * Create a value symbol builder. * * @param typeName the name of the type. * @param namespace the namespace of the type. * @return the symbol builder. */ public static Symbol.Builder createValueSymbolBuilder(String typeName, String namespace) { return createValueSymbolBuilder(typeName).namespace(namespace, "."); } /** * Create a pointable symbol builder. * * @param shape the shape that the type is for. * @param typeName the name of the type. * @param namespace the namespace of the type. * @return the symbol builder. */ public static Symbol.Builder createPointableSymbolBuilder(Shape shape, String typeName, String namespace) { return createPointableSymbolBuilder(shape, typeName).namespace(namespace, "."); } /** * Create a value symbol builder. * * @param shape the shape that the type is for. * @param typeName the name of the type. * @param namespace the namespace of the type. * @return the symbol builder. */ public static Symbol.Builder createValueSymbolBuilder(Shape shape, String typeName, String namespace) { return createValueSymbolBuilder(shape, typeName).namespace(namespace, "."); } /** * Create a pointable symbol builder. * * @param shape the shape that the type is for. * @param typeName the name of the type. * @param namespace the namespace of the type. * @return the symbol builder. */ public static Symbol.Builder createPointableSymbolBuilder(Shape shape, String typeName, GoDependency namespace) { return setImportedNamespace(createPointableSymbolBuilder(shape, typeName), namespace); } /** * Create a value symbol builder. * * @param shape the shape that the type is for. * @param typeName the name of the type. * @param namespace the namespace of the type. * @return the symbol builder. */ public static Symbol.Builder createValueSymbolBuilder(Shape shape, String typeName, GoDependency namespace) { return setImportedNamespace(createValueSymbolBuilder(shape, typeName), namespace); } /** * Create a pointable symbol builder. * * @param typeName the name of the type. * @param namespace the namespace of the type. * @return the symbol builder. */ public static Symbol.Builder createPointableSymbolBuilder(String typeName, GoDependency namespace) { return setImportedNamespace(createPointableSymbolBuilder(typeName), namespace); } /** * Create a value symbol builder. * * @param typeName the name of the type. * @param namespace the namespace of the type. * @return the symbol builder. */ public static Symbol.Builder createValueSymbolBuilder(String typeName, GoDependency namespace) { return setImportedNamespace(createValueSymbolBuilder(typeName), namespace); } private static Symbol.Builder setImportedNamespace(Symbol.Builder builder, GoDependency dependency) { return builder.namespace(dependency.getImportPath(), ".") .addDependency(dependency) .putProperty(NAMESPACE_ALIAS, dependency.getAlias()); } /** * Go declares several built-in language types in what is known as the Universe block. This function determines * whether the provided symbol represents a Go universe type. * * @param symbol the symbol to check * @return whether the symbol type is in the Go universe block * @see The Go Programming Language Specification */ public static boolean isUniverseType(Symbol symbol) { return symbol.getProperty(SymbolUtils.GO_UNIVERSE_TYPE, Boolean.class) .orElse(false); } public static boolean isPointable(Symbol symbol) { return symbol.getProperty(SymbolUtils.POINTABLE, Boolean.class).orElse(false); } public static Symbol getReference(Symbol symbol) { return symbol.getProperty(SymbolUtils.GO_ELEMENT_TYPE, Symbol.class).orElse(null); } /** * Builds a symbol within the context of the package in which codegen is taking place. * * @param name Symbol name. * @return The built symbol. */ public static Symbol buildPackageSymbol(String name) { return Symbol.builder().name(name).build(); } public static Symbol buildSymbol(String name, String namespace) { return Symbol.builder() .name(name) .namespace(namespace, ".") .build(); } public static boolean isNilable(Symbol symbol) { return isPointable(symbol) || symbol.getProperty(SymbolUtils.GO_SLICE).isPresent() || symbol.getProperty(SymbolUtils.GO_MAP).isPresent(); } public static Symbol pointerTo(Symbol symbol) { return symbol.toBuilder().putProperty(POINTABLE, true).build(); } public static Symbol sliceOf(Symbol symbol) { return symbol.toBuilder() .putProperty(GO_SLICE, true) .putProperty(GO_ELEMENT_TYPE, symbol) .build(); } } SymbolVisitor.java000066400000000000000000000522711463735525100351770ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.logging.Logger; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.ReservedWordSymbolProvider; import software.amazon.smithy.codegen.core.ReservedWords; import software.amazon.smithy.codegen.core.ReservedWordsBuilder; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; import software.amazon.smithy.go.codegen.trait.UnexportedMemberTrait; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.NeighborProviderIndex; import software.amazon.smithy.model.knowledge.NullableIndex; import software.amazon.smithy.model.neighbor.NeighborProvider; import software.amazon.smithy.model.neighbor.Relationship; import software.amazon.smithy.model.neighbor.RelationshipType; import software.amazon.smithy.model.shapes.BigDecimalShape; import software.amazon.smithy.model.shapes.BigIntegerShape; import software.amazon.smithy.model.shapes.BlobShape; import software.amazon.smithy.model.shapes.BooleanShape; import software.amazon.smithy.model.shapes.ByteShape; import software.amazon.smithy.model.shapes.CollectionShape; import software.amazon.smithy.model.shapes.DocumentShape; import software.amazon.smithy.model.shapes.DoubleShape; import software.amazon.smithy.model.shapes.FloatShape; import software.amazon.smithy.model.shapes.IntEnumShape; import software.amazon.smithy.model.shapes.IntegerShape; import software.amazon.smithy.model.shapes.ListShape; import software.amazon.smithy.model.shapes.LongShape; 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.ResourceShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.SetShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.ShapeVisitor; import software.amazon.smithy.model.shapes.ShortShape; import software.amazon.smithy.model.shapes.StringShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.TimestampShape; 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.traits.StreamingTrait; import software.amazon.smithy.utils.SmithyInternalApi; import software.amazon.smithy.utils.StringUtils; /** * Responsible for type mapping and file/identifier formatting. * *

Reserved words for Go are automatically escaped so that they are * suffixed with "_". See "reserved-words.txt" for the list of words. */ @SmithyInternalApi public final class SymbolVisitor implements SymbolProvider, ShapeVisitor { private static final Logger LOGGER = Logger.getLogger(SymbolVisitor.class.getName()); private final Model model; private final String rootModuleName; private final String typesPackageName; private final ReservedWordSymbolProvider.Escaper escaper; private final ReservedWordSymbolProvider.Escaper errorMemberEscaper; private final Map structureSpecificMemberEscapers = new HashMap<>(); private final GoPointableIndex pointableIndex; private final GoSettings settings; public SymbolVisitor(Model model, GoSettings settings) { this.model = model; this.settings = settings; this.rootModuleName = settings.getModuleName(); this.typesPackageName = this.rootModuleName + "/types"; this.pointableIndex = settings.getArtifactType() == GoSettings.ArtifactType.SERVER ? GoPointableIndex.of(model, NullableIndex.CheckMode.SERVER) : GoPointableIndex.of(model); // Reserve the generated names for union members, including the unknown case. ReservedWordsBuilder reservedNames = new ReservedWordsBuilder() .put(UnionGenerator.UNKNOWN_MEMBER_NAME, escapeWithTrailingUnderscore(UnionGenerator.UNKNOWN_MEMBER_NAME)); reserveUnionMemberNames(model, reservedNames); ReservedWords reservedMembers = new ReservedWordsBuilder() // Since Go only exports names if the first character is upper case and all // the go reserved words are lower case, it's functionally impossible to conflict, // so we only need to protect against common names. As of now there's only one. .put("String", "String_") .put("GetStream", "GetStream_") .build(); model.shapes(StructureShape.class) .filter(this::supportsInheritance) .forEach(this::reserveInterfaceMemberAccessors); escaper = ReservedWordSymbolProvider.builder() .nameReservedWords(reservedNames.build()) .memberReservedWords(reservedMembers) // Only escape words when the symbol has a definition file to // prevent escaping intentional references to built-in types. .escapePredicate((shape, symbol) -> !StringUtils.isEmpty(symbol.getDefinitionFile())) .buildEscaper(); // Reserved words that only apply to error members. ReservedWords reservedErrorMembers = new ReservedWordsBuilder() .put("ErrorCode", "ErrorCode_") .put("ErrorMessage", "ErrorMessage_") .put("ErrorFault", "ErrorFault_") .put("Unwrap", "Unwrap_") .put("Error", "Error_") .put("ErrorCodeOverride", "ErrorCodeOverride_") .build(); errorMemberEscaper = ReservedWordSymbolProvider.builder() .memberReservedWords(ReservedWords.compose(reservedMembers, reservedErrorMembers)) .escapePredicate((shape, symbol) -> !StringUtils.isEmpty(symbol.getDefinitionFile())) .buildEscaper(); } /** * Reserves generated member names for unions. * *

These have the format {UnionName}Member{MemberName}. * * @param model The model whose unions should be reserved. * @param builder A reserved words builder to add on to. */ private void reserveUnionMemberNames(Model model, ReservedWordsBuilder builder) { model.shapes(UnionShape.class).forEach(union -> { for (MemberShape member : union.getAllMembers().values()) { String memberName = formatUnionMemberName(union, member); builder.put(memberName, escapeWithTrailingUnderscore(memberName)); } }); } private boolean supportsInheritance(Shape shape) { return shape.isStructureShape() && shape.hasTrait(ErrorTrait.class); } /** * Reserves Get* and Has* member names for the given structure for use as accessor methods. * *

These reservations will only apply to the given structure, not to other structures. * * @param shape The structure shape whose members should be reserved. */ private void reserveInterfaceMemberAccessors(StructureShape shape) { ReservedWordsBuilder builder = new ReservedWordsBuilder(); for (MemberShape member : shape.getAllMembers().values()) { String name = getDefaultMemberName(member); String getterName = "Get" + name; String haserName = "Has" + name; builder.put(getterName, escapeWithTrailingUnderscore(getterName)); builder.put(haserName, escapeWithTrailingUnderscore(haserName)); } ReservedWordSymbolProvider.Escaper structureSpecificMemberEscaper = ReservedWordSymbolProvider.builder() .memberReservedWords(builder.build()) .buildEscaper(); structureSpecificMemberEscapers.put(shape.getId(), structureSpecificMemberEscaper); } private String escapeWithTrailingUnderscore(String symbolName) { return symbolName + "_"; } @Override public Symbol toSymbol(Shape shape) { Symbol symbol = shape.accept(this); LOGGER.fine(() -> String.format("Creating symbol from %s: %s", shape, symbol)); return linkArchetypeShape(shape, escaper.escapeSymbol(shape, symbol)); } /** * Links the archetype shape id for the symbol. * * @param shape the model shape * @param symbol the symbol to set the archetype property on * @return the symbol with archetype set if shape is a synthetic clone otherwise the original symbol */ private Symbol linkArchetypeShape(Shape shape, Symbol symbol) { return shape.getTrait(Synthetic.class) .map(synthetic -> symbol.toBuilder() .putProperty("archetype", synthetic.getArchetype()) .build()) .orElse(symbol); } @Override public String toMemberName(MemberShape shape) { Shape container = model.expectShape(shape.getContainer()); if (container.isUnionShape()) { // Union member names are not escaped as they are used to build the escape set. return formatUnionMemberName(container.asUnionShape().get(), shape); } String memberName = getDefaultMemberName(shape); memberName = escaper.escapeMemberName(memberName); // Escape words reserved for the specific container. if (structureSpecificMemberEscapers.containsKey(shape.getContainer())) { memberName = structureSpecificMemberEscapers.get(shape.getContainer()).escapeMemberName(memberName); } // Escape words that are only reserved for error members. if (isErrorMember(shape)) { memberName = errorMemberEscaper.escapeMemberName(memberName); } return memberName; } private String formatUnionMemberName(UnionShape union, MemberShape member) { return String.format("%sMember%s", getDefaultShapeName(union), getDefaultMemberName(member)); } private String getDefaultShapeName(Shape shape) { ServiceShape serviceShape = model.expectShape(settings.getService(), ServiceShape.class); return StringUtils.capitalize(removeLeadingInvalidIdentCharacters(shape.getId().getName(serviceShape))); } private String getDefaultMemberName(MemberShape shape) { String memberName = StringUtils.capitalize(removeLeadingInvalidIdentCharacters(shape.getMemberName())); // change to lowercase first character if unexported structure member. if (model.expectShape(shape.getContainer()).isStructureShape() && shape.hasTrait(UnexportedMemberTrait.class)) { memberName = Character.toLowerCase(memberName.charAt(0)) + memberName.substring(1); } return memberName; } private String removeLeadingInvalidIdentCharacters(String value) { if (Character.isAlphabetic(value.charAt(0))) { return value; } int i; for (i = 0; i < value.length(); i++) { if (Character.isAlphabetic(value.charAt(i))) { break; } } String remaining = value.substring(i); if (remaining.length() == 0) { throw new CodegenException("tried to clean name " + value + ", but resulted in empty string"); } return remaining; } private boolean isErrorMember(MemberShape shape) { return model.getShape(shape.getContainer()) .map(container -> container.hasTrait(ErrorTrait.ID)) .orElse(false); } @Override public Symbol blobShape(BlobShape shape) { if (shape.hasTrait(StreamingTrait.ID)) { Symbol inputVariant = symbolBuilderFor(shape, "Reader", SmithyGoDependency.IO).build(); return symbolBuilderFor(shape, "ReadCloser", SmithyGoDependency.IO) .putProperty(SymbolUtils.INPUT_VARIANT, inputVariant) .build(); } return symbolBuilderFor(shape, "[]byte") .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) .build(); } @Override public Symbol booleanShape(BooleanShape shape) { return symbolBuilderFor(shape, "bool") .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) .build(); } @Override public Symbol listShape(ListShape shape) { return createCollectionSymbol(shape); } @Override public Symbol setShape(SetShape shape) { // Go doesn't have a set type. Rather than hack together a set using a map, // we instead just create a list and let the service be responsible for // asserting that there are no duplicates. return createCollectionSymbol(shape); } private Symbol createCollectionSymbol(CollectionShape shape) { Symbol reference = toSymbol(shape.getMember()); // Shape name will be unused for symbols that represent a slice, but in the event it does we set the collection // shape's name to make debugging simpler. return symbolBuilderFor(shape, getDefaultShapeName(shape)) .putProperty(SymbolUtils.GO_SLICE, true) .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, reference.getProperty(SymbolUtils.GO_UNIVERSE_TYPE, Boolean.class).orElse(false)) .putProperty(SymbolUtils.GO_ELEMENT_TYPE, reference) .build(); } @Override public Symbol mapShape(MapShape shape) { Symbol reference = toSymbol(shape.getValue()); // Shape name will be unused for symbols that represent a map, but in the event it does we set the map shape's // name to make debugging simpler. return symbolBuilderFor(shape, getDefaultShapeName(shape)) .putProperty(SymbolUtils.GO_MAP, true) .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, reference.getProperty(SymbolUtils.GO_UNIVERSE_TYPE, Boolean.class).orElse(false)) .putProperty(SymbolUtils.GO_ELEMENT_TYPE, reference) .build(); } private Symbol.Builder symbolBuilderFor(Shape shape, String typeName) { if (pointableIndex.isPointable(shape)) { return SymbolUtils.createPointableSymbolBuilder(shape, typeName); } return SymbolUtils.createValueSymbolBuilder(shape, typeName); } private Symbol.Builder symbolBuilderFor(Shape shape, String typeName, GoDependency namespace) { if (pointableIndex.isPointable(shape)) { return SymbolUtils.createPointableSymbolBuilder(shape, typeName, namespace); } return SymbolUtils.createValueSymbolBuilder(shape, typeName, namespace); } private Symbol.Builder symbolBuilderFor(Shape shape, String typeName, String namespace) { if (pointableIndex.isPointable(shape)) { return SymbolUtils.createPointableSymbolBuilder(shape, typeName, namespace); } return SymbolUtils.createValueSymbolBuilder(shape, typeName, namespace); } @Override public Symbol byteShape(ByteShape shape) { return symbolBuilderFor(shape, "int8") .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) .build(); } @Override public Symbol shortShape(ShortShape shape) { return symbolBuilderFor(shape, "int16") .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) .build(); } @Override public Symbol integerShape(IntegerShape shape) { return symbolBuilderFor(shape, "int32") .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) .build(); } @Override public Symbol longShape(LongShape shape) { return symbolBuilderFor(shape, "int64") .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) .build(); } @Override public Symbol floatShape(FloatShape shape) { return symbolBuilderFor(shape, "float32") .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) .build(); } @Override public Symbol doubleShape(DoubleShape shape) { return symbolBuilderFor(shape, "float64") .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) .build(); } @Override public Symbol bigIntegerShape(BigIntegerShape shape) { return createBigSymbol(shape, "Int"); } @Override public Symbol bigDecimalShape(BigDecimalShape shape) { return createBigSymbol(shape, "Float"); } private Symbol createBigSymbol(Shape shape, String symbolName) { return symbolBuilderFor(shape, symbolName, SmithyGoDependency.BIG) .build(); } @Override public Symbol documentShape(DocumentShape shape) { return ProtocolDocumentGenerator.Utilities.getDocumentSymbolBuilder(settings, ProtocolDocumentGenerator.DOCUMENT_INTERFACE_NAME) .build(); } @Override public Symbol operationShape(OperationShape shape) { String name = getDefaultShapeName(shape); return SymbolUtils.createPointableSymbolBuilder(shape, name, rootModuleName) .definitionFile(String.format("./api_op_%s.go", name)) .build(); } @Override public Symbol resourceShape(ResourceShape shape) { // TODO: implement resources return SymbolUtils.createPointableSymbolBuilder(shape, "nil").build(); } @Override public Symbol serviceShape(ServiceShape shape) { return symbolBuilderFor(shape, "Client", rootModuleName) .definitionFile("./api_client.go") .build(); } @Override public Symbol stringShape(StringShape shape) { if (shape.hasTrait(EnumTrait.class)) { String name = getDefaultShapeName(shape); return symbolBuilderFor(shape, name, typesPackageName) .definitionFile("./types/enums.go") .build(); } return symbolBuilderFor(shape, "string") .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true) .build(); } @Override public Symbol structureShape(StructureShape shape) { String name = getDefaultShapeName(shape); if (shape.getId().getNamespace().equals(CodegenUtils.getSyntheticTypeNamespace())) { Optional boundOperationName = getNameOfBoundOperation(shape); if (boundOperationName.isPresent()) { return symbolBuilderFor(shape, name, rootModuleName) .definitionFile("./api_op_" + boundOperationName.get() + ".go") .build(); } } Symbol.Builder builder = symbolBuilderFor(shape, name, typesPackageName); if (shape.hasTrait(ErrorTrait.ID)) { builder.definitionFile("./types/errors.go"); } else { builder.definitionFile("./types/types.go"); } return builder.build(); } private Optional getNameOfBoundOperation(StructureShape shape) { NeighborProvider provider = NeighborProviderIndex.of(model).getReverseProvider(); for (Relationship relationship : provider.getNeighbors(shape)) { RelationshipType relationshipType = relationship.getRelationshipType(); if (relationshipType == RelationshipType.INPUT || relationshipType == RelationshipType.OUTPUT) { return Optional.of(getDefaultShapeName(relationship.getNeighborShape().get())); } } return Optional.empty(); } @Override public Symbol unionShape(UnionShape shape) { String name = getDefaultShapeName(shape); return symbolBuilderFor(shape, name, typesPackageName) .definitionFile("./types/types.go") .build(); } @Override public Symbol memberShape(MemberShape member) { Shape targetShape = model.expectShape(member.getTarget()); return toSymbol(targetShape) .toBuilder() .putProperty(SymbolUtils.POINTABLE, pointableIndex.isPointable(member)) .build(); } @Override public Symbol timestampShape(TimestampShape shape) { return symbolBuilderFor(shape, "Time", SmithyGoDependency.TIME).build(); } @Override public Symbol intEnumShape(IntEnumShape shape) { String name = getDefaultShapeName(shape); return symbolBuilderFor(shape, name, typesPackageName) .definitionFile("./types/enums.go") .build(); } } Synthetic.java000066400000000000000000000067201463735525100343220ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.Optional; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.traits.AbstractTrait; import software.amazon.smithy.model.traits.AbstractTraitBuilder; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.ToSmithyBuilder; /** * Defines a shape as being a clone of another modeled shape. *

* Must only be used as a runtime trait-only applied to shapes based on model processing */ public final class Synthetic extends AbstractTrait implements ToSmithyBuilder { public static final ShapeId ID = ShapeId.from("smithy.go.traits#Synthetic"); private static final String ARCHETYPE = "archetype"; private final Optional archetype; private Synthetic(Builder builder) { super(ID, builder.getSourceLocation()); this.archetype = builder.archetype; } /** * Get the archetype shape that this clone is based on. * * @return the original archetype shape */ public Optional getArchetype() { return archetype; } @Override protected Node createNode() { throw new CodegenException("attempted to serialize runtime only trait"); } @Override public boolean equals(Object other) { if (other == this) { return true; } else if (!(other instanceof Synthetic)) { return false; } else { Synthetic b = (Synthetic) other; return toShapeId().equals(b.toShapeId()) && archetype.equals(b.getArchetype()); } } @Override public int hashCode() { return toShapeId().hashCode() * 17 + Node.objectNode() .withOptionalMember(ARCHETYPE, archetype.map(ShapeId::toString).map(Node::from)) .hashCode(); } @Override public SmithyBuilder toBuilder() { Builder builder = builder(); getArchetype().ifPresent(builder::archetype); return builder; } /** * @return Returns a builder used to create {@link Synthetic}. */ public static Builder builder() { return new Builder(); } /** * Builder for {@link Synthetic}. */ public static final class Builder extends AbstractTraitBuilder { private Optional archetype = Optional.empty(); private Builder() { } @Override public Synthetic build() { return new Synthetic(this); } public Builder archetype(ShapeId archetype) { this.archetype = Optional.ofNullable(archetype); return this; } public Builder removeArchetype() { this.archetype = Optional.empty(); return this; } } } TriConsumer.java000066400000000000000000000027541463735525100346250ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.Objects; @FunctionalInterface public interface TriConsumer { /** * Performs the operation on the given inputs. * * @param t is the first argument * @param u is the second argument * @param v is the third argument */ void accept(T t, U u, V v); /** * Returns a composed {@link TriConsumer} that performs, in sequence, this * operation followed by the {@code after} operation. * * @param after the operation to perform after this operation * @return a composed {@link TriConsumer} * @throws NullPointerException if {@code after} is null */ default TriConsumer andThen(TriConsumer after) { Objects.requireNonNull(after); return (x, y, z) -> { accept(x, y, z); after.accept(x, y, z); }; } } UnionGenerator.java000066400000000000000000000173431463735525100353120ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.SimpleShape; import software.amazon.smithy.model.shapes.UnionShape; import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.SmithyInternalApi; /** * Renders unions and type aliases for all their members. */ @SmithyInternalApi public class UnionGenerator { public static final String UNKNOWN_MEMBER_NAME = "UnknownUnionMember"; private final Model model; private final SymbolProvider symbolProvider; private final UnionShape shape; private final boolean isEventStream; public UnionGenerator(Model model, SymbolProvider symbolProvider, UnionShape shape) { this.model = model; this.symbolProvider = symbolProvider; this.shape = shape; this.isEventStream = StreamingTrait.isEventStream(shape); } /** * Generates the Go type definitions for the UnionShape. * * @param writer the writer */ public void generateUnion(GoWriter writer) { Symbol symbol = symbolProvider.toSymbol(shape); Collection memberShapes = shape.getAllMembers().values() .stream() .filter(memberShape -> !isEventStreamErrorMember(memberShape)) .collect(Collectors.toCollection(TreeSet::new)); // Creates the parent interface for the union, which only defines a // non-exported method whose purpose is only to enable satisfying the // interface. if (writer.writeShapeDocs(shape)) { writer.writeDocs(""); } writer.writeDocs("The following types satisfy this interface:"); memberShapes.stream().map(symbolProvider::toMemberName).forEach(name -> { writer.write("// " + name); }); writer.openBlock("type $L interface {", "}", symbol.getName(), () -> { writer.write("is$L()", symbol.getName()); }).write(""); // Create structs for each member that satisfy the interface. for (MemberShape member : memberShapes) { Symbol memberSymbol = symbolProvider.toSymbol(member); String exportedMemberName = symbolProvider.toMemberName(member); Shape target = model.expectShape(member.getTarget()); // Create the member's concrete type writer.writeMemberDocs(model, member); writer.openBlock("type $L struct {", "}", exportedMemberName, () -> { // Union members can't have null values, so for simple shapes we don't // use pointers. We have to use pointers for complex shapes since, // for example, we could still have a map that's empty or which has // null values. if (target instanceof SimpleShape) { writer.write("Value $T", memberSymbol); } else { writer.write("Value $P", memberSymbol); } writer.write(""); writer.write("$L", ProtocolDocumentGenerator.NO_DOCUMENT_SERDE_TYPE_NAME); }); writer.write("func (*$L) is$L() {}", exportedMemberName, symbol.getName()); } } private boolean isEventStreamErrorMember(MemberShape memberShape) { return isEventStream && memberShape.getMemberTrait(model, ErrorTrait.class).isPresent(); } /** * Generates union usage examples for documentation. * * @param writer the writer */ public void generateUnionExamples(GoWriter writer) { Symbol symbol = symbolProvider.toSymbol(shape); Set members = shape.getAllMembers().values().stream() .filter(memberShape -> !isEventStreamErrorMember(memberShape)) .collect(Collectors.toCollection(TreeSet::new)); Set referenced = new HashSet<>(); writer.openBlock("func Example$L_outputUsage() {", "}", symbol.getName(), () -> { writer.write("var union $P", symbol); writer.writeDocs("type switches can be used to check the union value"); writer.openBlock("switch v := union.(type) {", "}", () -> { for (MemberShape member : members) { Symbol targetSymbol = symbolProvider.toSymbol(model.expectShape(member.getTarget())); referenced.add(targetSymbol); Symbol memberSymbol = SymbolUtils.createValueSymbolBuilder(symbolProvider.toMemberName(member), symbol.getNamespace()).build(); writer.openBlock("case *$T:", "", memberSymbol, () -> { writer.write("_ = v.Value // Value is $T", targetSymbol); }); } writer.addUseImports(SmithyGoDependency.FMT); Symbol unknownUnionMember = SymbolUtils.createPointableSymbolBuilder("UnknownUnionMember", symbol.getNamespace()).build(); writer.openBlock("case $P:", "", unknownUnionMember, () -> { writer.write("fmt.Println(\"unknown tag:\", v.Tag)"); }); writer.openBlock("default:", "", () -> { writer.write("fmt.Println(\"union is nil or unknown type\")"); }); }); }).write(""); referenced.forEach(s -> { writer.write("var _ $P", s); }); } /** * Generates a struct for unknown union values that applies to every union in the given set. * * @param writer The writer to write the union to. * @param unions A set of unions whose interfaces the union should apply to. * @param symbolProvider A symbol provider used to get the symbols for the unions. */ public static void generateUnknownUnion( GoWriter writer, Collection unions, SymbolProvider symbolProvider ) { // Creates a fallback type for use when an unknown member is found. This // could be the result of an outdated client, for example. writer.writeDocs(UNKNOWN_MEMBER_NAME + " is returned when a union member is returned over the wire, but has an unknown tag."); writer.openBlock("type $L struct {", "}", UNKNOWN_MEMBER_NAME, () -> { // The tag (member) name received over the wire. writer.write("Tag string"); // The value received. writer.write("Value []byte"); writer.write(""); writer.write("$L", ProtocolDocumentGenerator.NO_DOCUMENT_SERDE_TYPE_NAME); }); for (UnionShape union : unions) { writer.write("func (*$L) is$L() {}", UNKNOWN_MEMBER_NAME, symbolProvider.toSymbol(union).getName()); } } } UnresolvableProtocolException.java000066400000000000000000000015201463735525100404030ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import software.amazon.smithy.codegen.core.CodegenException; public class UnresolvableProtocolException extends CodegenException { public UnresolvableProtocolException(String message) { super(message); } } smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/000077500000000000000000000000001463735525100325205ustar00rootroot00000000000000AuthGenerator.java000066400000000000000000000035611463735525100360610ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.auth; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; /** * Entry point into smithy client auth generation. */ public class AuthGenerator { private final ProtocolGenerator.GenerationContext context; public AuthGenerator(ProtocolGenerator.GenerationContext context) { this.context = context; } public void generate() { if (context.getWriter().isEmpty()) { throw new CodegenException("writer is required"); } context.getWriter().get() .write("$W\n", new AuthParametersGenerator(context).generate()) .write("$W\n", new AuthParametersResolverGenerator(context).generate()) .write("$W\n", getResolverGenerator().generate()) .write("$W\n", new ResolveAuthSchemeMiddlewareGenerator(context).generate()) .write("$W\n", new GetIdentityMiddlewareGenerator(context).generate()) .write("$W\n", new SignRequestMiddlewareGenerator(context).generate()); } // TODO(i&a): allow consuming generators to overwrite private AuthSchemeResolverGenerator getResolverGenerator() { return new AuthSchemeResolverGenerator(context); } } AuthParameter.java000066400000000000000000000022611463735525100360470ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.auth; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoUniverseTypes; public record AuthParameter(String name, String docs, Symbol type) { public static final AuthParameter OPERATION = new AuthParameter( "Operation", "The name of the operation being invoked.", GoUniverseTypes.String ); public static final AuthParameter REGION = new AuthParameter( "Region", "The region in which the operation is being invoked.", GoUniverseTypes.String ); } AuthParametersGenerator.java000066400000000000000000000066131463735525100401060ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.auth; import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.ArrayList; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.MapUtils; /** * Generates auth scheme resolver parameters. * By default, the only field that exists universally is the name of the operation being invoked. Services that use * SigV4[A] will also have a field for the region. * Additional parameters can be loaded via GoIntegration. */ public class AuthParametersGenerator { public static final String STRUCT_NAME = "AuthResolverParameters"; public static final Symbol STRUCT_SYMBOL = SymbolUtils.createPointableSymbolBuilder(STRUCT_NAME).build(); private final ProtocolGenerator.GenerationContext context; private final ArrayList fields = new ArrayList<>( ListUtils.of(AuthParameter.OPERATION) ); public AuthParametersGenerator(ProtocolGenerator.GenerationContext context) { this.context = context; } public GoWriter.Writable generate() { loadFields(); return goTemplate( """ $doc:W type $name:L struct { $fields:W } """, MapUtils.of( "doc", generateDocs(), "name", STRUCT_NAME, "fields", generateFields() ) ); } private GoWriter.Writable generateDocs() { return goDocTemplate( "$name:L contains the set of inputs necessary for auth scheme resolution.", MapUtils.of("name", STRUCT_NAME) ); } private GoWriter.Writable generateFields() { return (writer) -> { for (var field: fields) { writer.write(""" $W $L $P """, goDocTemplate(field.docs()), field.name(), field.type() ); } }; } private void loadFields() { for (var integration: context.getIntegrations()) { var plugins = integration.getClientPlugins().stream().filter(it -> it.matchesService(context.getModel(), context.getService())).toList(); for (var plugin: plugins) { fields.addAll(plugin.getAuthParameters()); } } } } AuthParametersResolver.java000066400000000000000000000013441463735525100377550ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.auth; import software.amazon.smithy.codegen.core.Symbol; public record AuthParametersResolver(Symbol resolver) { } AuthParametersResolverGenerator.java000066400000000000000000000056241463735525100416310ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.auth; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.ArrayList; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.utils.MapUtils; /** * Generates a single method which binds auth scheme resolver parameters from operation input and client options. * The only value bound by default is the operation name. Generators must load additional bindings through * GoIntegration. */ public class AuthParametersResolverGenerator { public static final String FUNC_NAME = "bindAuthResolverParams"; private final ProtocolGenerator.GenerationContext context; private final ArrayList resolvers = new ArrayList<>(); public AuthParametersResolverGenerator(ProtocolGenerator.GenerationContext context) { this.context = context; } public GoWriter.Writable generate() { loadResolvers(); return goTemplate(""" func $name:L(ctx $context:T, operation string, input interface{}, options Options) $params:P { params := &$params:T{ Operation: operation, } $bindings:W return params } """, MapUtils.of( "name", FUNC_NAME, "params", AuthParametersGenerator.STRUCT_SYMBOL, "bindings", generateResolvers(), "context", GoStdlibTypes.Context.Context )); } private GoWriter.Writable generateResolvers() { return (writer) -> { for (var resolver: resolvers) { writer.write("$T(ctx, params, input, options)", resolver.resolver()); } }; } private void loadResolvers() { for (var integration: context.getIntegrations()) { var plugins = integration.getClientPlugins().stream().filter(it -> it.matchesService(context.getModel(), context.getService())).toList(); for (var plugin: plugins) { resolvers.addAll(plugin.getAuthParameterResolvers()); } } } } AuthSchemeResolverGenerator.java000066400000000000000000000201771463735525100407320ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.auth; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.Map; import java.util.stream.Collectors; import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.model.knowledge.ServiceIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.utils.MapUtils; /** * Implements modeled auth scheme resolver generation. */ public class AuthSchemeResolverGenerator { public static final String INTERFACE_NAME = "AuthSchemeResolver"; public static final String DEFAULT_NAME = "defaultAuthSchemeResolver"; private final ProtocolGenerator.GenerationContext context; private final ServiceIndex serviceIndex; private final Map schemeDefinitions; public AuthSchemeResolverGenerator(ProtocolGenerator.GenerationContext context) { this.context = context; this.serviceIndex = ServiceIndex.of(context.getModel()); this.schemeDefinitions = context.getIntegrations().stream() .flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream()) .flatMap(it -> it.getAuthSchemeDefinitions().entrySet().stream()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } // an operation has auth overrides if any of the following are true: // 1. its list of supported schemes differs from that of the service // 2. its auth optionality differs from that of the service (covered by checking [1] w/ NO_AUTH_AWARE) // 3. it has an unsigned payload private boolean hasAuthOverrides(OperationShape operation) { var serviceSchemes = serviceIndex .getEffectiveAuthSchemes(context.getService(), ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) .keySet(); var operationSchemes = serviceIndex .getEffectiveAuthSchemes(context.getService(), operation, ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) .keySet(); return !serviceSchemes.equals(operationSchemes) || operation.hasTrait(UnsignedPayloadTrait.class); } public GoWriter.Writable generate() { return goTemplate(""" $W $W """, generateInterface(), generateDefault()); } private GoWriter.Writable generateInterface() { return goTemplate(""" $W type $L interface { ResolveAuthSchemes($T, *$L) ([]$P, error) } """, generateDocs(), INTERFACE_NAME, GoStdlibTypes.Context.Context, AuthParametersGenerator.STRUCT_NAME, SmithyGoTypes.Auth.Option); } private GoWriter.Writable generateDocs() { return goDocTemplate("AuthSchemeResolver returns a set of possible authentication options for an " + "operation."); } private GoWriter.Writable generateDefault() { return goTemplate(""" $W $W """, generateDefaultStruct(), generateDefaultResolve()); } private GoWriter.Writable generateDefaultStruct() { return goTemplate(""" type $1L struct{} var _ $2L = (*$1L)(nil) """, DEFAULT_NAME, INTERFACE_NAME); } private GoWriter.Writable generateDefaultResolve() { return goTemplate(""" func (*$receiver:L) ResolveAuthSchemes(ctx $ctx:L, params *$params:L) ([]$options:P, error) { if overrides, ok := operationAuthOptions[params.Operation]; ok { return overrides(params), nil } return serviceAuthOptions(params), nil } $opAuthOptions:W $svcAuthOptions:W """, MapUtils.of( "receiver", DEFAULT_NAME, "ctx", GoStdlibTypes.Context.Context, "params", AuthParametersGenerator.STRUCT_NAME, "options", SmithyGoTypes.Auth.Option, "opAuthOptions", generateOperationAuthOptions(), "svcAuthOptions", generateServiceAuthOptions())); } private GoWriter.Writable generateOperationAuthOptions() { var options = new GoWriter.ChainWritable(); TopDownIndex.of(context.getModel()) .getContainedOperations(context.getService()).stream() .filter(this::hasAuthOverrides) .forEach(it -> { options.add(generateOperationAuthOptionsEntry(it)); }); return goTemplate(""" var operationAuthOptions = map[string]func(*$L) []$P{ $W } """, AuthParametersGenerator.STRUCT_NAME, SmithyGoTypes.Auth.Option, options.compose()); } private GoWriter.Writable generateOperationAuthOptionsEntry(OperationShape operation) { var options = new GoWriter.ChainWritable(); serviceIndex .getEffectiveAuthSchemes(context.getService(), operation, ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) .entrySet().stream() .filter(it -> schemeDefinitions.containsKey(it.getKey())) .forEach(it -> { var definition = schemeDefinitions.get(it.getKey()); options.add(definition.generateOperationOption(context, operation)); }); return options.isEmpty() ? emptyGoTemplate() : goTemplate(""" $1S: func(params *$2L) []$3P { return []$3P{ $4W } },""", operation.getId().getName(), AuthParametersGenerator.STRUCT_NAME, SmithyGoTypes.Auth.Option, options.compose()); } private GoWriter.Writable generateServiceAuthOptions() { var options = new GoWriter.ChainWritable(); serviceIndex .getEffectiveAuthSchemes(context.getService(), ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) .entrySet().stream() .filter(it -> schemeDefinitions.containsKey(it.getKey())) .forEach(it -> { var definition = schemeDefinitions.get(it.getKey()); options.add(definition.generateServiceOption(context, context.getService())); }); return goTemplate(""" func serviceAuthOptions(params *$1L) []$2P { return []$2P{ $3W } } """, AuthParametersGenerator.STRUCT_NAME, SmithyGoTypes.Auth.Option, options.compose()); } } GetIdentityMiddlewareGenerator.java000066400000000000000000000103111463735525100413760ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.auth; import static software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator.createFinalizeStepMiddleware; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.MiddlewareIdentifier; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.utils.MapUtils; public class GetIdentityMiddlewareGenerator { public static final String MIDDLEWARE_NAME = "getIdentityMiddleware"; public static final String MIDDLEWARE_ID = "GetIdentity"; private final ProtocolGenerator.GenerationContext context; public GetIdentityMiddlewareGenerator(ProtocolGenerator.GenerationContext context) { this.context = context; } public static GoWriter.Writable generateAddToProtocolFinalizers() { return goTemplate(""" if err := stack.Finalize.Insert(&$L{options: options}, $S, $T); err != nil { return $T("add $L: %v", err) } """, MIDDLEWARE_NAME, ResolveAuthSchemeMiddlewareGenerator.MIDDLEWARE_ID, SmithyGoTypes.Middleware.After, GoStdlibTypes.Fmt.Errorf, MIDDLEWARE_ID); } public GoWriter.Writable generate() { return GoWriter.ChainWritable.of( createFinalizeStepMiddleware(MIDDLEWARE_NAME, MiddlewareIdentifier.string(MIDDLEWARE_ID)) .asWritable(generateBody(), generateFields()), generateContextFuncs() ).compose(); } private GoWriter.Writable generateFields() { return goTemplate(""" options Options """); } private GoWriter.Writable generateBody() { return goTemplate(""" rscheme := getResolvedAuthScheme(ctx) if rscheme == nil { return out, metadata, $errorf:T("no resolved auth scheme") } resolver := rscheme.Scheme.IdentityResolver(m.options) if resolver == nil { return out, metadata, $errorf:T("no identity resolver") } identity, err := resolver.GetIdentity(ctx, rscheme.IdentityProperties) if err != nil { return out, metadata, $errorf:T("get identity: %w", err) } ctx = setIdentity(ctx, identity) return next.HandleFinalize(ctx, in) """, MapUtils.of( "errorf", GoStdlibTypes.Fmt.Errorf )); } private GoWriter.Writable generateContextFuncs() { return goTemplate(""" type identityKey struct{} func setIdentity(ctx $context:T, identity $identity:T) $context:T { return $withStackValue:T(ctx, identityKey{}, identity) } func getIdentity(ctx $context:T) $identity:T { v, _ := $getStackValue:T(ctx, identityKey{}).($identity:T) return v } """, MapUtils.of( "context", GoStdlibTypes.Context.Context, "withStackValue", SmithyGoTypes.Middleware.WithStackValue, "getStackValue", SmithyGoTypes.Middleware.GetStackValue, "identity", SmithyGoTypes.Auth.Identity )); } } ResolveAuthSchemeMiddlewareGenerator.java000066400000000000000000000150361463735525100425440ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.auth; import static software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator.createFinalizeStepMiddleware; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.MiddlewareIdentifier; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.utils.MapUtils; public class ResolveAuthSchemeMiddlewareGenerator { public static final String MIDDLEWARE_NAME = "resolveAuthSchemeMiddleware"; public static final String MIDDLEWARE_ID = "ResolveAuthScheme"; private final ProtocolGenerator.GenerationContext context; public ResolveAuthSchemeMiddlewareGenerator(ProtocolGenerator.GenerationContext context) { this.context = context; } public static GoWriter.Writable generateAddToProtocolFinalizers() { return goTemplate(""" if err := stack.Finalize.Add(&$L{operation: operation, options: options}, $T); err != nil { return $T("add $L: %w", err) } """, MIDDLEWARE_NAME, SmithyGoTypes.Middleware.Before, GoStdlibTypes.Fmt.Errorf, MIDDLEWARE_ID); } public GoWriter.Writable generate() { return GoWriter.ChainWritable.of( createFinalizeStepMiddleware(MIDDLEWARE_NAME, MiddlewareIdentifier.string(MIDDLEWARE_ID)) .asWritable(generateBody(), generateFields()), generateSelectScheme(), generateContextFuncs() ).compose(); } private GoWriter.Writable generateFields() { return goTemplate(""" operation string options Options """); } private GoWriter.Writable generateBody() { return goTemplate(""" params := $1L(ctx, m.operation, getOperationInput(ctx), m.options) options, err := m.options.AuthSchemeResolver.ResolveAuthSchemes(ctx, params) if err != nil { return out, metadata, $2T("resolve auth scheme: %w", err) } scheme, ok := m.selectScheme(options) if !ok { return out, metadata, $2T("could not select an auth scheme") } ctx = setResolvedAuthScheme(ctx, scheme) return next.HandleFinalize(ctx, in) """, AuthParametersResolverGenerator.FUNC_NAME, GoStdlibTypes.Fmt.Errorf ); } private GoWriter.Writable generateSelectScheme() { return goTemplate(""" func (m *$middlewareName:L) selectScheme(options []$option:P) (*resolvedAuthScheme, bool) { for _, option := range options { if option.SchemeID == $schemeIDAnonymous:T { return newResolvedAuthScheme($newAnonymousScheme:T(), option), true } for _, scheme := range m.options.AuthSchemes { if scheme.SchemeID() != option.SchemeID { continue } if scheme.IdentityResolver(m.options) != nil { return newResolvedAuthScheme(scheme, option), true } } } return nil, false } """, MapUtils.of( "middlewareName", MIDDLEWARE_NAME, "option", SmithyGoTypes.Auth.Option, "schemeIDAnonymous", SmithyGoTypes.Auth.SchemeIDAnonymous, "newAnonymousScheme", SmithyGoTypes.Transport.Http.NewAnonymousScheme ) ); } private GoWriter.Writable generateContextFuncs() { return goTemplate(""" type resolvedAuthSchemeKey struct{} type resolvedAuthScheme struct { Scheme $authScheme:T IdentityProperties $properties:T SignerProperties $properties:T } func newResolvedAuthScheme(scheme $authScheme:T, option $option:P) *resolvedAuthScheme { return &resolvedAuthScheme{ Scheme: scheme, IdentityProperties: option.IdentityProperties, SignerProperties: option.SignerProperties, } } func setResolvedAuthScheme(ctx $context:T, scheme *resolvedAuthScheme) $context:T { return $withStackValue:T(ctx, resolvedAuthSchemeKey{}, scheme) } func getResolvedAuthScheme(ctx $context:T) *resolvedAuthScheme { v, _ := $getStackValue:T(ctx, resolvedAuthSchemeKey{}).(*resolvedAuthScheme) return v } """, MapUtils.of( "authScheme", getAuthSchemeSymbol(), "option", SmithyGoTypes.Auth.Option, "properties", SmithyGoTypes.Smithy.Properties, "context", GoStdlibTypes.Context.Context, "withStackValue", SmithyGoTypes.Middleware.WithStackValue, "getStackValue", SmithyGoTypes.Middleware.GetStackValue )); } // FUTURE(#458): when protocols are defined here, they should supply the auth scheme symbol, for // now it's pinned to the HTTP variant private Symbol getAuthSchemeSymbol() { return SmithyGoTypes.Transport.Http.AuthScheme; } } SignRequestMiddlewareGenerator.java000066400000000000000000000073571463735525100414360ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.auth; import static software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator.createFinalizeStepMiddleware; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.MiddlewareIdentifier; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.endpoints.EndpointMiddlewareGenerator; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.utils.MapUtils; public class SignRequestMiddlewareGenerator { public static final String MIDDLEWARE_NAME = "signRequestMiddleware"; public static final String MIDDLEWARE_ID = "Signing"; private final ProtocolGenerator.GenerationContext context; public SignRequestMiddlewareGenerator(ProtocolGenerator.GenerationContext context) { this.context = context; } public static GoWriter.Writable generateAddToProtocolFinalizers() { return goTemplate(""" if err := stack.Finalize.Insert(&$L{}, $S, $T); err != nil { return $T("add $L: %w", err) } """, MIDDLEWARE_NAME, EndpointMiddlewareGenerator.MIDDLEWARE_ID, SmithyGoTypes.Middleware.After, GoStdlibTypes.Fmt.Errorf, MIDDLEWARE_ID); } public GoWriter.Writable generate() { return createFinalizeStepMiddleware(MIDDLEWARE_NAME, MiddlewareIdentifier.string(MIDDLEWARE_ID)) .asWritable(generateBody(), generateFields()); } private GoWriter.Writable generateFields() { return emptyGoTemplate(); } private GoWriter.Writable generateBody() { return goTemplate(""" req, ok := in.Request.($request:P) if !ok { return out, metadata, $errorf:T("unexpected transport type %T", in.Request) } rscheme := getResolvedAuthScheme(ctx) if rscheme == nil { return out, metadata, $errorf:T("no resolved auth scheme") } identity := getIdentity(ctx) if identity == nil { return out, metadata, $errorf:T("no identity") } signer := rscheme.Scheme.Signer() if signer == nil { return out, metadata, $errorf:T("no signer") } if err := signer.SignRequest(ctx, req, identity, rscheme.SignerProperties); err != nil { return out, metadata, $errorf:T("sign request: %w", err) } return next.HandleFinalize(ctx, in) """, MapUtils.of( // FUTURE(#458) protocol generator should specify the transport type "request", SmithyGoTypes.Transport.Http.Request, "errorf", GoStdlibTypes.Fmt.Errorf )); } } endpoints/000077500000000000000000000000001463735525100335035ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegenAuthSchemePropertyGenerator.java000066400000000000000000000113421463735525100420110ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.rulesengine.language.syntax.Identifier; import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.RecordLiteral; import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.StringLiteral; import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.TupleLiteral; /** * Handles codegen for the `authSchemes` endpoint property. This property is represented in codegen as `[]*auth.Option`. */ public class AuthSchemePropertyGenerator { private final ExpressionGenerator generator; public AuthSchemePropertyGenerator(ExpressionGenerator generator) { this.generator = generator; } public static String mapEndpointPropertyAuthSchemeName(String name) { return switch (name) { case "sigv4" -> "aws.auth#sigv4"; case "sigv4a" -> "aws.auth#sigv4a"; default -> name; }; } public GoWriter.Writable generate(Expression expr) { return goTemplate(""" $T(&out, []$P{ $W }) """, SmithyGoTypes.Auth.SetAuthOptions, SmithyGoTypes.Auth.Option, GoWriter.ChainWritable.of( ((TupleLiteral) expr).members().stream() .map(it -> generateOption(generator, (RecordLiteral) it)) .toList() ).compose(false)); } private GoWriter.Writable generateOption(ExpressionGenerator generator, RecordLiteral scheme) { var members = scheme.members(); var schemeName = ((StringLiteral) members.get(Identifier.of("name"))).value().expectLiteral(); return goTemplate(""" { SchemeID: $1S, SignerProperties: func() $2T { var sp $2T $3W return sp }(), },""", mapEndpointPropertyAuthSchemeName(schemeName), SmithyGoTypes.Smithy.Properties, generateOptionSignerProps(generator, scheme)); } private GoWriter.Writable generateOptionSignerProps(ExpressionGenerator generator, RecordLiteral scheme) { var props = new GoWriter.ChainWritable(); scheme.members().forEach((ident, expr) -> { var name = ident.getName().expectStringNode().getValue(); switch (name) { // properties that don't apply to the scheme would just be ignored by the signer impl. case "signingName" -> props.add(goTemplate(""" $1T(&sp, $3W) $2T(&sp, $3W)""", SmithyGoTypes.Transport.Http.SetSigV4SigningName, SmithyGoTypes.Transport.Http.SetSigV4ASigningName, generator.generate(expr))); case "signingRegion" -> props.add(goTemplate("$T(&sp, $W)", SmithyGoTypes.Transport.Http.SetSigV4SigningRegion, generator.generate(expr))); case "signingRegionSet" -> { var regions = GoWriter.ChainWritable.of( ((TupleLiteral) expr).members().stream() .map(generator::generate) .toList() ).compose(); props.add(goTemplate("$T(&sp, []string{$W})", SmithyGoTypes.Transport.Http.SetSigV4ASigningRegions, regions)); } case "disableDoubleEncoding" -> props.add(goTemplate("$T(&sp, $W)", SmithyGoTypes.Transport.Http.SetDisableDoubleEncoding, generator.generate(expr))); default -> { return; } } }); return props.compose(); } } EndpointClientPluginsGenerator.java000066400000000000000000000101631463735525100424770ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import static software.amazon.smithy.go.codegen.endpoints.EndpointParametersGenerator.parameterAsSymbol; import java.util.ArrayList; import java.util.List; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.go.codegen.integration.ConfigField; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; import software.amazon.smithy.model.Model; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter; import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.StringUtils; /** * Class responsible for registering client config fields that * are modeled in endpoint rulesets. */ public class EndpointClientPluginsGenerator implements GoIntegration { private final List runtimeClientPlugins = new ArrayList<>(); private static String getExportedParameterName(Parameter parameter) { return StringUtils.capitalize(parameter.getName().getName().getValue()); } @Override public List getClientPlugins() { runtimeClientPlugins.add(RuntimeClientPlugin.builder() .configFields(ListUtils.of( ConfigField.builder() .name("BaseEndpoint") .type(SymbolUtils.createPointableSymbolBuilder("string") .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true).build()) .documentation( """ This endpoint will be given as input to an EndpointResolverV2. It is used for providing a custom base endpoint that is subject to modifications by the processing EndpointResolverV2. """ ) .build() )) .build()); return runtimeClientPlugins; } @Override public void processFinalizedModel(GoSettings settings, Model model) { var service = settings.getService(model); if (!service.hasTrait(EndpointRuleSetTrait.class) || !service.hasTrait(ClientContextParamsTrait.class)) { return; } var ruleset = EndpointRuleSet.fromNode(service.expectTrait(EndpointRuleSetTrait.class).getRuleSet()); var ccParams = service.expectTrait(ClientContextParamsTrait.class).getParameters(); var parameters = ruleset.getParameters(); parameters.forEach(param -> { var ccParam = ccParams.get(param.getName().getName().getValue()); if (ccParam == null || param.getBuiltIn().isPresent()) { return; } runtimeClientPlugins.add( RuntimeClientPlugin.builder() .addConfigField( ConfigField.builder() .name(getExportedParameterName(param)) .type(parameterAsSymbol(param)) .documentation(ccParam.getDocumentation().orElse("")) .build() ) .build() ); }); } } EndpointMiddlewareGenerator.java000066400000000000000000000147241463735525100420030ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import static software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator.createFinalizeStepMiddleware; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.MiddlewareIdentifier; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.auth.GetIdentityMiddlewareGenerator; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; import software.amazon.smithy.utils.MapUtils; /** * Class responsible for generating middleware * that will be used during endpoint resolution. */ public final class EndpointMiddlewareGenerator { public static final String MIDDLEWARE_NAME = "resolveEndpointV2Middleware"; public static final String MIDDLEWARE_ID = "ResolveEndpointV2"; private final ProtocolGenerator.GenerationContext context; public EndpointMiddlewareGenerator(ProtocolGenerator.GenerationContext context) { this.context = context; } public static GoWriter.Writable generateAddToProtocolFinalizers() { return goTemplate(""" if err := stack.Finalize.Insert(&$L{options: options}, $S, $T); err != nil { return $T("add $L: %v", err) } """, MIDDLEWARE_NAME, GetIdentityMiddlewareGenerator.MIDDLEWARE_ID, SmithyGoTypes.Middleware.After, GoStdlibTypes.Fmt.Errorf, MIDDLEWARE_ID); } public GoWriter.Writable generate() { return createFinalizeStepMiddleware(MIDDLEWARE_NAME, MiddlewareIdentifier.string(MIDDLEWARE_ID)) .asWritable(generateBody(), generateFields()); } private GoWriter.Writable generateFields() { return goTemplate(""" options Options """); } private GoWriter.Writable generateBody() { if (!context.getService().hasTrait(EndpointRuleSetTrait.class)) { return goTemplate("return next.HandleFinalize(ctx, in)"); } return goTemplate(""" $pre:W $assertRequest:W $assertResolver:W $resolveEndpoint:W $mergeAuthProperties:W $post:W return next.HandleFinalize(ctx, in) """, MapUtils.of( "pre", generatePreResolutionHooks(), "assertRequest", generateAssertRequest(), "assertResolver", generateAssertResolver(), "resolveEndpoint", generateResolveEndpoint(), "mergeAuthProperties", generateMergeAuthProperties(), "post", generatePostResolutionHooks() )); } private GoWriter.Writable generatePreResolutionHooks() { return (GoWriter writer) -> { for (GoIntegration integration : context.getIntegrations()) { integration.renderPreEndpointResolutionHook(context.getSettings(), writer, context.getModel()); } }; } private GoWriter.Writable generateAssertRequest() { return goTemplate(""" req, ok := in.Request.($P) if !ok { return out, metadata, $T("unknown transport type %T", in.Request) } """, SmithyGoTypes.Transport.Http.Request, GoStdlibTypes.Fmt.Errorf); } private GoWriter.Writable generateAssertResolver() { return goTemplate(""" if m.options.EndpointResolverV2 == nil { return out, metadata, $T("expected endpoint resolver to not be nil") } """, GoStdlibTypes.Fmt.Errorf); } private GoWriter.Writable generateResolveEndpoint() { return goTemplate(""" params := bindEndpointParams(ctx, getOperationInput(ctx), m.options) endpt, err := m.options.EndpointResolverV2.ResolveEndpoint(ctx, *params) if err != nil { return out, metadata, $1T("failed to resolve service endpoint, %w", err) } if endpt.URI.RawPath == "" && req.URL.RawPath != "" { endpt.URI.RawPath = endpt.URI.Path } req.URL.Scheme = endpt.URI.Scheme req.URL.Host = endpt.URI.Host req.URL.Path = $2T(endpt.URI.Path, req.URL.Path) req.URL.RawPath = $2T(endpt.URI.RawPath, req.URL.RawPath) for k := range endpt.Headers { req.Header.Set(k, endpt.Headers.Get(k)) } """, GoStdlibTypes.Fmt.Errorf, SmithyGoTypes.Transport.Http.JoinPath); } private GoWriter.Writable generateMergeAuthProperties() { return goTemplate(""" rscheme := getResolvedAuthScheme(ctx) if rscheme == nil { return out, metadata, $T("no resolved auth scheme") } opts, _ := $T(&endpt.Properties) for _, o := range opts { rscheme.SignerProperties.SetAll(&o.SignerProperties) } """, GoStdlibTypes.Fmt.Errorf, SmithyGoTypes.Auth.GetAuthOptions); } private GoWriter.Writable generatePostResolutionHooks() { return (GoWriter writer) -> { for (GoIntegration integration : context.getIntegrations()) { integration.renderPostEndpointResolutionHook(context.getSettings(), writer, context.getModel()); } }; } } EndpointParameterBindingsGenerator.java000066400000000000000000000120071463735525100433140ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter; import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; import software.amazon.smithy.utils.MapUtils; /** * Generates the main endpoint parameter binding function. Operation-specific bindings are generated elsewhere * conditionally through EndpointParameterOperationBindingsGenerator. */ public class EndpointParameterBindingsGenerator { private final ProtocolGenerator.GenerationContext context; private final Map builtinBindings; public EndpointParameterBindingsGenerator(ProtocolGenerator.GenerationContext context) { this.context = context; this.builtinBindings = context.getIntegrations().stream() .flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream()) .flatMap(it -> it.getEndpointBuiltinBindings().entrySet().stream()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } public GoWriter.Writable generate() { if (!context.getService().hasTrait(EndpointRuleSetTrait.class)) { return emptyGoTemplate(); } return goTemplate(""" type endpointParamsBinder interface { bindEndpointParams(*EndpointParameters) } func bindEndpointParams(ctx $context:T, input interface{}, options Options) *EndpointParameters { params := &EndpointParameters{} $builtinBindings:W $clientContextBindings:W if b, ok := input.(endpointParamsBinder); ok { b.bindEndpointParams(params) } return params } """, MapUtils.of( "context", GoStdlibTypes.Context.Context, "builtinBindings", generateBuiltinBindings(), "clientContextBindings", generateClientContextBindings() )); } private GoWriter.Writable generateBuiltinBindings() { var bindings = new HashMap(); for (var integration: context.getIntegrations()) { var plugins = integration.getClientPlugins(context.getModel(), context.getService()); for (var plugin: plugins) { bindings.putAll(plugin.getEndpointBuiltinBindings()); } } var params = new ArrayList(); context.getEndpointRules().getParameters().forEach(params::add); var boundBuiltins = params.stream() .filter(it -> it.isBuiltIn() && bindings.containsKey(it.getBuiltIn().get())) .toList(); return writer -> { for (var param: boundBuiltins) { writer.write( "params.$L = $W", EndpointParametersGenerator.getExportedParameterName(param), builtinBindings.get(param.getBuiltIn().get())); } }; } private GoWriter.Writable generateClientContextBindings() { if (!context.getService().hasTrait(ClientContextParamsTrait.class)) { return goTemplate(""); } var allParams = new ArrayList(); context.getEndpointRules().getParameters().forEach(allParams::add); var contextParams = context.getService().expectTrait(ClientContextParamsTrait.class).getParameters(); var params = allParams.stream() .filter(it -> contextParams.containsKey(it.getName().getName().getValue()) && !it.isBuiltIn()) .toList(); return writer -> { params.forEach(it -> { writer.write("params.$1L = options.$1L", EndpointParametersGenerator.getExportedParameterName(it)); }); }; } } EndpointParameterOperationBindingsGenerator.java000066400000000000000000000167221463735525100452050ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.utils.StringUtils.capitalize; import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoJmespathExpressionGenerator; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; import software.amazon.smithy.jmespath.JmespathExpression; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.language.syntax.Identifier; import software.amazon.smithy.rulesengine.language.syntax.parameters.ParameterType; import software.amazon.smithy.rulesengine.traits.ContextParamTrait; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; import software.amazon.smithy.rulesengine.traits.OperationContextParamDefinition; import software.amazon.smithy.rulesengine.traits.OperationContextParamsTrait; import software.amazon.smithy.rulesengine.traits.StaticContextParamDefinition; import software.amazon.smithy.rulesengine.traits.StaticContextParamsTrait; /** * Generates operation-specific bindings (@operationContextParam, @contextParam, @staticContextParam) as a receiver * method on the operation's input structure. */ public class EndpointParameterOperationBindingsGenerator { private final GoCodegenContext ctx; private final OperationShape operation; private final StructureShape input; private final EndpointRuleSet rules; public EndpointParameterOperationBindingsGenerator( GoCodegenContext ctx, OperationShape operation, StructureShape input ) { this.ctx = ctx; this.operation = operation; this.input = input; this.rules = ctx.settings().getService(ctx.model()) .expectTrait(EndpointRuleSetTrait.class) .getEndpointRuleSet(); } private boolean hasBindings() { var hasContextBindings = input.getAllMembers().values().stream().anyMatch(it -> it.hasTrait(ContextParamTrait.class)); return hasContextBindings || operation.hasTrait(StaticContextParamsTrait.class) || operation.hasTrait(OperationContextParamsTrait.class); } public GoWriter.Writable generate() { if (!hasBindings()) { return goTemplate(""); } return goTemplate(""" func (in $P) bindEndpointParams(p *EndpointParameters) { $W $W $W } """, ctx.symbolProvider().toSymbol(input), generateOperationContextParamBindings(), generateContextParamBindings(), generateStaticContextParamBindings()); } private GoWriter.Writable generateOperationContextParamBindings() { if (!operation.hasTrait(OperationContextParamsTrait.class)) { return emptyGoTemplate(); } var params = operation.expectTrait(OperationContextParamsTrait.class); return GoWriter.ChainWritable.of( params.getParameters().entrySet().stream() .map(it -> generateOpContextParamBinding(it.getKey(), it.getValue())) .toList() ).compose(false); } private GoWriter.Writable generateOpContextParamBinding(String paramName, OperationContextParamDefinition def) { var param = rules.getParameters().get(Identifier.of(paramName)).get(); var expr = JmespathExpression.parse(def.getPath()); return writer -> { var generator = new GoJmespathExpressionGenerator(ctx, writer, input, expr); writer.write("func() {"); // contain the scope for each binding var result = generator.generate("in"); if (param.getType().equals(ParameterType.STRING_ARRAY)) { // projections can result in either []string OR []*string -- if the latter, we have to unwrap var target = result.shape().asListShape().get().getMember().getTarget(); if (GoPointableIndex.of(ctx.model()).isPointable(target)) { writer.write(""" deref := []string{} for _, v := range $L { if v != nil { deref = append(deref, *v) } } p.$L = deref""", result.ident(), capitalize(paramName)); } else { writer.write("p.$L = $L", capitalize(paramName), result.ident()); } } else { writer.write("p.$L = $L", capitalize(paramName), result.ident()); } writer.write("}()"); }; } private GoWriter.Writable generateContextParamBindings() { return writer -> { input.getAllMembers().values().forEach(it -> { if (!it.hasTrait(ContextParamTrait.class)) { return; } var contextParam = it.expectTrait(ContextParamTrait.class); writer.write("p.$L = in.$L", contextParam.getName(), it.getMemberName()); }); }; } private GoWriter.Writable generateStaticContextParamBindings() { if (!operation.hasTrait(StaticContextParamsTrait.class)) { return goTemplate(""); } StaticContextParamsTrait params = operation.expectTrait(StaticContextParamsTrait.class); return writer -> { params.getParameters().forEach((k, v) -> { writer.write("p.$L = $W", capitalize(k), generateStaticLiteral(v)); }); }; } private GoWriter.Writable generateStaticLiteral(StaticContextParamDefinition literal) { return writer -> { Node value = literal.getValue(); if (value.isStringNode()) { writer.writeInline("$T($S)", SmithyGoTypes.Ptr.String, value.expectStringNode().getValue()); } else if (value.isBooleanNode()) { writer.writeInline("$T($L)", SmithyGoTypes.Ptr.Bool, value.expectBooleanNode().getValue()); } else { writer.writeInline("[]string{$W}", GoWriter.ChainWritable.of( value.expectArrayNode().getElements().stream() .map(it -> goTemplate("$S,", it.expectStringNode().getValue())) .toList() ).compose(false)); } }; } } EndpointParametersGenerator.java000066400000000000000000000300511463735525100420200ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goBlockTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.SymbolUtils.pointerTo; import static software.amazon.smithy.go.codegen.SymbolUtils.sliceOf; import java.io.Serializable; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; import java.util.stream.StreamSupport; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoUniverseTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.node.StringNode; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.language.evaluation.value.Value; import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter; import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.StringUtils; public final class EndpointParametersGenerator { public static final String VALIDATE_REQUIRED_FUNC_NAME = "ValidateRequired"; public static final String DEFAULT_VALUE_FUNC_NAME = "WithDefaults"; private final Map commonCodegenArgs; private EndpointParametersGenerator(Builder builder) { var parametersType = SmithyBuilder.requiredState("parametersType", builder.parametersType); commonCodegenArgs = MapUtils.of( "parametersType", parametersType, "fmtErrorf", SymbolUtils.createValueSymbolBuilder("Errorf", SmithyGoDependency.FMT).build()); } public GoWriter.Writable generate(Optional ruleset) { if (ruleset.isPresent()) { var parameters = ruleset.get().getParameters(); return generateParameters(MapUtils.of( "parametersMembers", generateParametersMembers(parameters), "parametersValidationMethod", generateValidationMethod(parameters), "parametersDefaultValueMethod", generateDefaultsMethod(parameters))); } else { return generateParameters(MapUtils.of( "parametersMembers", emptyGoTemplate(), "parametersValidationMethod", emptyGoTemplate(), "parametersDefaultValueMethod", emptyGoTemplate())); } } private GoWriter.Writable generateParameters(Map overriddenArgs) { return goTemplate(""" $parametersTypeDocs:W type $parametersType:T struct { $parametersMembers:W } $parametersValidationMethod:W $parametersDefaultValueMethod:W """, commonCodegenArgs, MapUtils.of( "parametersTypeDocs", generateParametersTypeDocs()), overriddenArgs); } private GoWriter.Writable generateParametersTypeDocs() { return goDocTemplate(""" $parametersType:T provides the parameters that influence how endpoints are resolved. """, commonCodegenArgs); } private GoWriter.Writable generateParametersMembers(Parameters parameters) { return (GoWriter w) -> { w.indent(); parameters.forEach((parameter) -> { var writeChain = new GoWriter.ChainWritable() .add(parameter.getDocumentation(), GoWriter::goTemplate) // TODO[GH-25977529]: fix incorrect wrapping in generated comment .add(parameter.isRequired(), goTemplate("Parameter is required.")) .add(parameter.getDefault(), (defaultValue) -> { return goTemplate("Defaults to " + defaultValue + " if no value is provided."); }) .add(parameter.getBuiltIn(), GoWriter::goTemplate) .add(parameter.getDeprecated(), (deprecated) -> { return goTemplate("Deprecated: " + deprecated.getMessage()); }); w.writeRenderedDocs(writeChain.compose()); w.write("$L $P", getExportedParameterName(parameter), parameterAsSymbol(parameter)); w.write(""); }); }; } private GoWriter.Writable generateDefaultsMethod(Parameters parameters) { return goTemplate(""" $methodDocs:W func (p $parametersType:T) $funcName:L() $parametersType:T { $setDefaults:W return p } """, commonCodegenArgs, MapUtils.of( "funcName", DEFAULT_VALUE_FUNC_NAME, "methodDocs", goDocTemplate("$funcName:L returns a shallow copy of $parametersType:T" + "with default values applied to members where applicable.", commonCodegenArgs, MapUtils.of( "funcName", DEFAULT_VALUE_FUNC_NAME)), "setDefaults", (GoWriter.Writable) (GoWriter w) -> { sortParameters(parameters).forEach((parameter) -> { parameter.getDefault().ifPresent(defaultValue -> { w.writeGoTemplate(""" if p.$memberName:L == nil { p.$memberName:L = $defaultValue:W } """, MapUtils.of( "memberName", getExportedParameterName(parameter), "defaultValue", generateDefaultValue(parameter, defaultValue) )); }); }); })); } private GoWriter.Writable generateDefaultValue(Parameter parameter, Value defaultValue) { return switch (parameter.getType()) { case STRING -> goTemplate("$T($S)", SmithyGoDependency.SMITHY_PTR.func("String"), defaultValue.expectStringValue()); case BOOLEAN -> goTemplate("$T($L)", SmithyGoDependency.SMITHY_PTR.func("Bool"), defaultValue.expectBooleanValue()); case STRING_ARRAY -> goTemplate("[]string{$W}", GoWriter.ChainWritable.of( defaultValue.expectArrayValue().getValues().stream() .map(it -> goTemplate("$S,", it.expectStringValue())) .toList() ).compose(false)); }; } private GoWriter.Writable generateValidationMethod(Parameters parameters) { if (!haveRequiredParameters(parameters)) { return emptyGoTemplate(); } return goBlockTemplate(""" $methodDocs:W func (p $parametersType:T) $funcName:L() error { """, "}", commonCodegenArgs, MapUtils.of( "funcName", VALIDATE_REQUIRED_FUNC_NAME, "methodDocs", goDocTemplate(""" $funcName:L validates required parameters are set. """, MapUtils.of( "funcName", VALIDATE_REQUIRED_FUNC_NAME))), (w) -> { sortParameters(parameters).forEach((parameter) -> { if (!parameter.isRequired()) { return; } w.writeGoTemplate(""" if p.$memberName:L == nil { return $fmtErrorf:T("parameter $memberName:L is required") } """, commonCodegenArgs, MapUtils.of( "memberName", getExportedParameterName(parameter))); }); w.write("return nil"); }); } public static Symbol parameterAsSymbol(Parameter parameter) { return switch (parameter.getType()) { case STRING -> pointerTo(GoUniverseTypes.String); case BOOLEAN -> pointerTo(GoUniverseTypes.Bool); case STRING_ARRAY -> sliceOf(GoUniverseTypes.String); }; } public static boolean haveRequiredParameters(Parameters parameters) { for (Iterator iter = parameters.iterator(); iter.hasNext();) { if (iter.next().isRequired()) { return true; } } return false; } public static String getExportedParameterName(Parameter parameter) { return StringUtils.capitalize(parameter.getName().getName().getValue()); } public static String getExportedParameterName(StringNode name) { return StringUtils.capitalize(name.getValue()); } public static Builder builder() { return new Builder(); } public static final class Builder implements SmithyBuilder { private Symbol parametersType; private Builder() { } public Builder parametersType(Symbol parametersType) { this.parametersType = parametersType; return this; } @Override public EndpointParametersGenerator build() { return new EndpointParametersGenerator(this); } } public static Stream sortParameters(Parameters parameters) { return StreamSupport .stream(parameters.spliterator(), false) .sorted(new Sorted()); } public static final class Sorted implements Comparator, Serializable { /** * Initializes the SortedMembers. */ @Override public int compare(Parameter a, Parameter b) { // first compare if the options are required or not, whichever option is // required should win. If both // options are required or not required, continue on to alphabetic search. // If a is required but b isn't return -1 so a is sorted before b // If b is required but a isn't, return 1 so a is sorted after b // If both a and b are required or optional, use alphabetic sorting of a and b's // member name. int requiredOption = 0; if (a.isRequired()) { requiredOption -= 1; } if (b.isRequired()) { requiredOption += 1; } if (requiredOption != 0) { return requiredOption; } return a.getName().getName().getValue().compareTo(b.getName().getName().getValue()); } } } EndpointResolutionGenerator.java000066400000000000000000000126711463735525100420700ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import java.util.ArrayList; import java.util.List; import java.util.Optional; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; import software.amazon.smithy.rulesengine.traits.EndpointTestCase; import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait; /** * Generates all components required for Smithy Ruleset Endpoint Resolution. * These components include a Provider, Parameters, and Tests. */ public class EndpointResolutionGenerator { public static final String FEATURE_NAME = "V2"; public static final String PARAMETERS_TYPE_NAME = "EndpointParameters"; public static final String RESOLVER_INTERFACE_NAME = "Endpoint" + "Resolver" + FEATURE_NAME; public static final String RESOLVER_IMPLEMENTATION_NAME = "resolver"; public static final String RESOLVER_ENDPOINT_METHOD_NAME = "ResolveEndpoint"; public static final String NEW_RESOLVER_FUNC_NAME = "NewDefault" + RESOLVER_INTERFACE_NAME; private final FnProvider fnProvider; private final Symbol endpointType; private final Symbol parametersType; private final Symbol resolverInterfaceType; private final Symbol resolverImplementationType; private final Symbol newResolverFn; public EndpointResolutionGenerator(FnProvider fnProvider) { this.fnProvider = fnProvider; this.endpointType = SymbolUtils.createValueSymbolBuilder("Endpoint", SmithyGoDependency.SMITHY_ENDPOINTS).build(); this.parametersType = SymbolUtils.createValueSymbolBuilder(PARAMETERS_TYPE_NAME).build(); this.resolverInterfaceType = SymbolUtils.createValueSymbolBuilder(RESOLVER_INTERFACE_NAME).build(); this.resolverImplementationType = SymbolUtils.createValueSymbolBuilder(RESOLVER_IMPLEMENTATION_NAME).build(); this.newResolverFn = SymbolUtils.createValueSymbolBuilder(NEW_RESOLVER_FUNC_NAME).build(); } public void generate(ProtocolGenerator.GenerationContext context) { if (context.getWriter().isEmpty()) { throw new CodegenException("writer is required"); } var writer = context.getWriter().get(); var serviceShape = context.getService(); var parametersGenerator = EndpointParametersGenerator.builder() .parametersType(parametersType) .build(); var resolverGenerator = EndpointResolverGenerator.builder() .parametersType(parametersType) .resolverInterfaceType(resolverInterfaceType) .resolverImplementationType(resolverImplementationType) .newResolverFn(newResolverFn) .endpointType(endpointType) .resolveEndpointMethodName(RESOLVER_ENDPOINT_METHOD_NAME) .fnProvider(this.fnProvider) .build(); var bindingGenerator = new EndpointParameterBindingsGenerator(context); var middlewareGenerator = new EndpointMiddlewareGenerator(context); Optional ruleset = serviceShape.getTrait(EndpointRuleSetTrait.class) .map((trait) -> EndpointRuleSet.fromNode(trait.getRuleSet())); writer .write("$W\n", parametersGenerator.generate(ruleset)) .write("$W\n", resolverGenerator.generate(ruleset)) .write("$W\n", bindingGenerator.generate()) .write("$W", middlewareGenerator.generate()); } public void generateTests(ProtocolGenerator.GenerationContext context) { if (context.getWriter().isEmpty()) { throw new CodegenException("writer is required"); } var writer = context.getWriter().get(); var serviceShape = context.getService(); Optional ruleset = serviceShape.getTrait(EndpointRuleSetTrait.class) .map((trait) -> EndpointRuleSet.fromNode(trait.getRuleSet())); var testsGenerator = EndpointTestsGenerator.builder() .parametersType(parametersType) .newResolverFn(newResolverFn) .endpointType(endpointType) .resolveEndpointMethodName(RESOLVER_ENDPOINT_METHOD_NAME) .build(); final List testCases = new ArrayList<>(); var endpointTestTrait = serviceShape.getTrait(EndpointTestsTrait.class); endpointTestTrait.ifPresent(trait -> testCases.addAll(trait.getTestCases())); writer.write("$W", testsGenerator.generate(ruleset, testCases)); } } EndpointResolverGenerator.java000066400000000000000000000633251463735525100415300ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goBlockTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.GoWriter.joinWritables; import static software.amazon.smithy.go.codegen.endpoints.EndpointParametersGenerator.VALIDATE_REQUIRED_FUNC_NAME; import static software.amazon.smithy.go.codegen.endpoints.EndpointParametersGenerator.getExportedParameterName; import static software.amazon.smithy.go.codegen.endpoints.EndpointParametersGenerator.haveRequiredParameters; import static software.amazon.smithy.go.codegen.endpoints.FnGenerator.isFnResultOptional; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.TreeMap; import java.util.logging.Logger; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.SourceException; import software.amazon.smithy.model.SourceLocation; import software.amazon.smithy.rulesengine.language.Endpoint; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.language.error.RuleError; import software.amazon.smithy.rulesengine.language.evaluation.type.OptionalType; import software.amazon.smithy.rulesengine.language.syntax.Identifier; import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; import software.amazon.smithy.rulesengine.language.syntax.expressions.ExpressionVisitor; import software.amazon.smithy.rulesengine.language.syntax.expressions.Reference; import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionDefinition; import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.IsSet; import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.Literal; import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter; import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters; import software.amazon.smithy.rulesengine.language.syntax.rule.Condition; import software.amazon.smithy.rulesengine.language.syntax.rule.Rule; import software.amazon.smithy.rulesengine.language.syntax.rule.RuleValueVisitor; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyBuilder; public final class EndpointResolverGenerator { private static final String PARAMS_ARG_NAME = "params"; private static final String REALIZED_URL_VARIABLE_NAME = "uri"; private static final String ERROR_MESSAGE_ENDOFTREE = "Endpoint resolution failed. Invalid operation or environment input."; private static final Logger LOGGER = Logger.getLogger(EndpointResolverGenerator.class.getName()); private final Map commonCodegenArgs; private final FnProvider fnProvider; private int conditionIdentCounter = 0; private EndpointResolverGenerator(Builder builder) { var parametersType = SmithyBuilder.requiredState("parametersType", builder.parametersType); var resolverInterfaceType = SmithyBuilder.requiredState("resolverInterfaceType", builder.resolverInterfaceType); var resolverImplementationType = SmithyBuilder.requiredState("resolverImplementationType", builder.resolverImplementationType); var newResolverFn = SmithyBuilder.requiredState("newResolverFn", builder.newResolverFn); var endpointType = SmithyBuilder.requiredState("endpointType", builder.endpointType); var resolveEndpointMethodName = SmithyBuilder.requiredState("resolveEndpointMethodName", builder.resolveEndpointMethodName); this.fnProvider = SmithyBuilder.requiredState("fnProvider", builder.fnProvider); this.commonCodegenArgs = MapUtils.of( "paramArgName", PARAMS_ARG_NAME, "parametersType", parametersType, "endpointType", endpointType, "resolverInterfaceType", resolverInterfaceType, "resolverImplementationType", resolverImplementationType, "newResolverFn", newResolverFn, "resolveEndpointMethodName", resolveEndpointMethodName, "fmtErrorf", SymbolUtils.createValueSymbolBuilder("Errorf", SmithyGoDependency.FMT).build()); } public GoWriter.Writable generate(Optional ruleset) { if (ruleset.isPresent()) { return generateResolverType(generateResolveMethodBody(ruleset.get())); } else { LOGGER.warning("service does not have modeled endpoint rules"); return generateEmptyRules(); } } public GoWriter.Writable generateEmptyRules() { return generateResolverType(generateEmptyResolveMethodBody()); } private GoWriter.Writable generateResolverType(GoWriter.Writable resolveMethodBody) { return goTemplate(""" $stringSlice:W // $resolverInterfaceType:T provides the interface for resolving service endpoints. type $resolverInterfaceType:T interface { $resolveEndpointMethodDocs:W $resolveEndpointMethodName:L(ctx $context:T, $paramArgName:L $parametersType:T) ( $endpointType:T, error, ) } $resolverTypeDocs:W type $resolverImplementationType:T struct{} func $newResolverFn:T() $resolverInterfaceType:T { return &$resolverImplementationType:T{} } $resolveEndpointMethodDocs:W func (r *$resolverImplementationType:T) $resolveEndpointMethodName:L( ctx $context:T, $paramArgName:L $parametersType:T, ) ( endpoint $endpointType:T, err error, ) { $resolveMethodBody:W } """, commonCodegenArgs, MapUtils.of( "context", SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT).build(), "stringSlice", generateStringSliceHelper(), "resolverTypeDocs", generateResolverTypeDocs(), "resolveEndpointMethodDocs", generateResolveEndpointMethodDocs(), "resolveMethodBody", resolveMethodBody)); } private static String getLocalVarParameterName(Parameter p) { return "_" + p.getName().toString(); } private static String getMemberParameterName(Parameter p) { return PARAMS_ARG_NAME + "." + getExportedParameterName(p); } private GoWriter.Writable generateResolveMethodBody(EndpointRuleSet ruleset) { var scope = Scope.empty(); for (Iterator iter = ruleset.getParameters().iterator(); iter.hasNext();) { // Required parameters can be dereferenced directly so that read access are // always by value. // Optional parameters will be dereferenced via conditional checks. String identName; Parameter p = iter.next(); if (p.isRequired()) { identName = getLocalVarParameterName(p); } else { identName = getMemberParameterName(p); } scope = scope.withIdent(p.toExpression(), identName); } ruleset.typeCheck(); return goTemplate(""" $paramsWithDefaults:W $validateParams:W $paramVars:W $rules:W """, commonCodegenArgs, MapUtils.of( "logPrintln", SymbolUtils.createValueSymbolBuilder("Println", SmithyGoDependency.LOG).build(), "validateParams", generateValidateParams(ruleset.getParameters()), "paramsWithDefaults", generateParamsWithDefaults(), "paramVars", (GoWriter.Writable) (GoWriter w) -> { for (Iterator iter = ruleset.getParameters().iterator(); iter.hasNext();) { Parameter param = iter.next(); if (!param.isRequired()) { continue; } switch (param.getType()) { case STRING, BOOLEAN -> w.write("$L := *$L", getLocalVarParameterName(param), getMemberParameterName(param)); case STRING_ARRAY -> { w.write("$L := stringSlice($L)", getLocalVarParameterName(param), getMemberParameterName(param)); } default -> throw new CodegenException("unrecognized parameter type"); } } }, "rules", generateRulesList(ruleset.getRules(), scope))); } private GoWriter.Writable generateParamsWithDefaults() { return goTemplate("$paramArgName:L = $paramArgName:L.$withDefaults:L()", commonCodegenArgs, MapUtils.of( "withDefaults", EndpointParametersGenerator.DEFAULT_VALUE_FUNC_NAME)); } private GoWriter.Writable generateEmptyResolveMethodBody() { return goTemplate("return endpoint, $fmtErrorf:T(\"no endpoint rules defined\")", commonCodegenArgs); } private GoWriter.Writable generateResolverTypeDocs() { return goDocTemplate("$resolverImplementationType:T provides the implementation for resolving endpoints.", commonCodegenArgs); } private GoWriter.Writable generateResolveEndpointMethodDocs() { return goDocTemplate("$resolveEndpointMethodName:L attempts to resolve the endpoint with the provided options," + " returning the endpoint if found. Otherwise an error is returned.", commonCodegenArgs); } private GoWriter.Writable generateValidateParams(Parameters parameters) { if (!haveRequiredParameters(parameters)) { return emptyGoTemplate(); } return goTemplate(""" if err = $paramArgName:L.$paramsValidateMethod:L(); err != nil { return endpoint, $fmtErrorf:T("endpoint parameters are not valid, %w", err) } """, commonCodegenArgs, MapUtils.of( "paramsValidateMethod", VALIDATE_REQUIRED_FUNC_NAME)); } private GoWriter.Writable generateRulesList(List rules, Scope scope) { return (w) -> { rules.forEach(rule -> { rule.getDocumentation().ifPresent(w::writeDocs); w.write("$W", generateRule(rule, rule.getConditions(), scope)); }); if (!rules.isEmpty()) { Rule lastRule = rules.get(rules.size() - 1); // Trees are terminal, so we must ensure there's a final fallback condition at // the end of each one. // Generally we know we need to insert one when the final rule in a tree is not // "static" i.e. it has // conditions that might mean it is not selected. Since it may not be chosen // (its set of conditions may // evaluate to false) we MUST put a fallback error return after which we know // will get executed. // // However, assignment statements are conflated with conditions in the rules // language, and while certain // assignments DO have a condition associated with them (basically, checking // that the result of the // assignment is not nil), some do not. Therefore, remove "static" // condition/assignments from // consideration. boolean needsFallback = !lastRule.getConditions().stream().filter( condition -> { // You can't assert into a FunctionDefinition from an Expression - we have to // inspect the fn // member of the node directly. String fn = condition.toNode().expectObjectNode().expectStringMember("fn").getValue(); // the only static assignment condition, as of this writing... return !fn.equals("uriEncode"); }).toList().isEmpty(); if (needsFallback) { w.writeGoTemplate( "return endpoint, $fmtErrorf:T(\"" + ERROR_MESSAGE_ENDOFTREE + "\")", commonCodegenArgs); } } }; } private GoWriter.Writable generateRule(Rule rule, List conditions, Scope scope) { if (conditions.isEmpty()) { return rule.accept(new RuleVisitor(scope, this.fnProvider)); } var condition = conditions.get(0); var remainingConditions = conditions.subList(1, conditions.size()); var generator = new ExpressionGenerator(scope, this.fnProvider); var fn = conditionalFunc(condition); String conditionIdentifier; if (condition.getResult().isPresent()) { var ident = condition.getResult().get(); conditionIdentifier = "_" + ident.getName().getValue(); // Store the condition result so that it can be referenced in the future by the // result identifier. scope = scope.withIdent(new Reference(ident, SourceLocation.NONE), conditionIdentifier); } else { conditionIdentifier = nameForExpression(fn); } if (fn.type() instanceof OptionalType || isConditionalFnResultOptional(condition, fn)) { return goTemplate(""" if exprVal := $target:W; exprVal != nil { $conditionIdent:L := *exprVal _ = $conditionIdent:L $next:W } """, MapUtils.of( "conditionIdent", conditionIdentifier, "target", generator.generate(fn), "next", generateRule( rule, remainingConditions, scope.withMember(fn, conditionIdentifier)))); } if (condition.getResult().isPresent()) { return goTemplate(""" $conditionIdent:L := $target:W _ = $conditionIdent:L $next:W """, MapUtils.of( "conditionIdent", conditionIdentifier, "target", generator.generate(fn), "next", generateRule( rule, remainingConditions, scope.withMember(fn, conditionIdentifier)))); } return goTemplate(""" if $target:W { $next:W } """, MapUtils.of( "target", generator.generate(fn), "next", generateRule(rule, remainingConditions, scope))); } private static Expression conditionalFunc(Condition condition) { var fn = condition.getFunction(); if (fn instanceof IsSet) { var setFn = ((IsSet) fn); List argv = setFn.getArguments(); if (argv.size() == 1) { return argv.get(0); } throw new RuleError(new SourceException("expected 1 argument but found " + argv.size(), setFn)); } return fn; } private String nameForExpression(Expression expr) { conditionIdentCounter++; if (expr instanceof Reference) { return nameForRef((Reference) expr); } return String.format("_var_%d", conditionIdentCounter); } /** * Returns a name for a reference. * * @param ref reference to get name for * @return name */ private static String nameForRef(Reference ref) { return "_" + ref.getName(); } private GoWriter.Writable generateEndpoint(Endpoint endpoint, Scope scope) { return goTemplate(""" $endpointType:T{ URI: *$uriVariableName:L, $headers:W $properties:W } """, commonCodegenArgs, MapUtils.of( "uriVariableName", REALIZED_URL_VARIABLE_NAME, "headers", generateEndpointHeaders(endpoint.getHeaders(), scope), "properties", generateEndpointProperties(endpoint.getProperties(), scope))); } private GoWriter.Writable generateEndpointHeaders(Map> headers, Scope scope) { Map args = MapUtils.of( "memberName", "Headers", "headerType", SymbolUtils.createPointableSymbolBuilder("Header", SmithyGoDependency.NET_HTTP).build(), "newHeaders", SymbolUtils.createValueSymbolBuilder("Header{}", SmithyGoDependency.NET_HTTP).build()); // TODO: consider removing this line (letting it default to nil init) // rather than generating empty headers // https://github.com/aws/aws-sdk-go-v2/pull/2110/files#r1186193501 if (headers.isEmpty()) { return goTemplate("Headers: $newHeaders:T,", args); } var writableHeaders = new TreeMap>(); var generator = new ExpressionGenerator(scope, this.fnProvider); headers.forEach((k, vs) -> { var writables = new ArrayList(); vs.forEach(v -> writables.add(generator.generate(v))); writableHeaders.put(k, writables); }); return goBlockTemplate("$memberName:L: func() $headerType:T {", "}(),", args, (w) -> { w.writeGoTemplate("headers := $newHeaders:T", args); writableHeaders.forEach((k, vs) -> { w.write("headers.Set($W)", generateNewHeaderValue(k, vs)); }); w.write("return headers"); }); } private GoWriter.Writable generateNewHeaderValue(String headerName, List headerValues) { Map args = MapUtils.of( "headerName", headerName, "headerValues", joinWritables(headerValues, ", ")); if (headerValues.isEmpty()) { return goTemplate("$headerName:W", args); } return goTemplate("$headerName:S, $headerValues:W", args); } private GoWriter.Writable generateEndpointProperties(Map properties, Scope scope) { if (properties.isEmpty()) { return emptyGoTemplate(); } var generator = new ExpressionGenerator(scope, this.fnProvider); return goTemplate(""" Properties: func() $1T { var out $1T $2W return out }(), """, SmithyGoTypes.Smithy.Properties, GoWriter.ChainWritable.of( properties.entrySet().stream() .map(it -> generateSetProperty(generator, it.getKey(), it.getValue())) .toList() ).compose(false)); } private GoWriter.Writable generateSetProperty(ExpressionGenerator generator, Identifier ident, Expression expr) { // FUTURE: add these via GoIntegration? return ident.toString().equals("authSchemes") ? new AuthSchemePropertyGenerator(generator).generate(expr) : goTemplate("out.Set($S, $W)", ident.toString(), generator.generate(expr)); } class RuleVisitor implements RuleValueVisitor { final Scope scope; final FnProvider fnProvider; RuleVisitor(Scope scope, FnProvider fnProvider) { this.scope = scope; this.fnProvider = fnProvider; } @Override public GoWriter.Writable visitTreeRule(List rules) { return generateRulesList(rules, scope); } @Override public GoWriter.Writable visitErrorRule(Expression errorExpr) { return goTemplate(""" return endpoint, $fmtErrorf:T("endpoint rule error, %s", $errorExpr:W) """, commonCodegenArgs, MapUtils.of( "errorExpr", new ExpressionGenerator(scope, fnProvider).generate(errorExpr))); } @Override public GoWriter.Writable visitEndpointRule(Endpoint endpoint) { return goTemplate(""" uriString := $url:W $uriVariableName:L, err := url.Parse(uriString) if err != nil { return endpoint, fmt.Errorf(\"Failed to parse uri: %s\", uriString) } return $endpoint:W, nil """, MapUtils.of( // TODO: consider simplifying how the URI string is built // look into strings.Join "uriVariableName", REALIZED_URL_VARIABLE_NAME, "url", new ExpressionGenerator(scope, this.fnProvider).generate(endpoint.getUrl()), "endpoint", generateEndpoint(endpoint, scope))); } } private static boolean isConditionalFnResultOptional(Condition condition, Expression fn) { if (condition.getResult().isEmpty()) { return false; } final boolean[] isOptionalResult = {false}; fn.accept(new ExpressionVisitor.Default() { @Override public Void getDefault() { return null; } @Override public Void visitLibraryFunction(FunctionDefinition fn, List args) { isOptionalResult[0] = isFnResultOptional(fn); return null; } }); return isOptionalResult[0]; } public static Builder builder() { return new Builder(); } public static final class Builder implements SmithyBuilder { private Symbol resolverInterfaceType; private Symbol resolverImplementationType; private Symbol newResolverFn; private Symbol parametersType; private Symbol endpointType; private String resolveEndpointMethodName; private FnProvider fnProvider; private Builder() { } public Builder endpointType(Symbol endpointType) { this.endpointType = endpointType; return this; } public Builder resolverInterfaceType(Symbol resolverInterfaceType) { this.resolverInterfaceType = resolverInterfaceType; return this; } public Builder resolverImplementationType(Symbol resolverImplementationType) { this.resolverImplementationType = resolverImplementationType; return this; } public Builder newResolverFn(Symbol newResolverFn) { this.newResolverFn = newResolverFn; return this; } public Builder resolveEndpointMethodName(String resolveEndpointMethodName) { this.resolveEndpointMethodName = resolveEndpointMethodName; return this; } public Builder parametersType(Symbol parametersType) { this.parametersType = parametersType; return this; } public Builder fnProvider(FnProvider fnProvider) { this.fnProvider = fnProvider; return this; } @Override public EndpointResolverGenerator build() { return new EndpointResolverGenerator(this); } } private GoWriter.Writable generateStringSliceHelper() { return goTemplate(""" type stringSlice []string func (s stringSlice) Get(i int) *string { if i < 0 || i >= len(s) { return nil } v := s[i] return &v }"""); } } EndpointTestsGenerator.java000066400000000000000000000401561463735525100410260ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goBlockTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.GoWriter.joinWritables; import static software.amazon.smithy.go.codegen.endpoints.EndpointParametersGenerator.getExportedParameterName; import static software.amazon.smithy.go.codegen.util.NodeUtil.writableStringSlice; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.BooleanNode; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.NodeVisitor; import software.amazon.smithy.model.node.NullNode; import software.amazon.smithy.model.node.NumberNode; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.node.StringNode; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters; import software.amazon.smithy.rulesengine.traits.EndpointTestCase; import software.amazon.smithy.rulesengine.traits.ExpectedEndpoint; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyBuilder; public final class EndpointTestsGenerator { private final Map commonCodegenArgs; private EndpointTestsGenerator(Builder builder) { var newResolverFn = SmithyBuilder.requiredState("newResolverFn", builder.newResolverFn); var parametersType = SmithyBuilder.requiredState("parametersType", builder.parametersType); var endpointType = SmithyBuilder.requiredState("endpointType", builder.endpointType); var resolveEndpointMethodName = SmithyBuilder.requiredState("resolveEndpointMethodName", builder.resolveEndpointMethodName); commonCodegenArgs = MapUtils.of( "parametersType", parametersType, "endpointType", endpointType, "newResolverFn", newResolverFn, "resolveEndpointMethodName", resolveEndpointMethodName, "fmtErrorf", SymbolUtils.createValueSymbolBuilder("Errorf", SmithyGoDependency.FMT).build()); } public GoWriter.Writable generate(Optional ruleset, List testCases) { if (!ruleset.isPresent()) { return emptyGoTemplate(); } var parameters = ruleset.get().getParameters(); List writables = new ArrayList<>(); for (int i = 0; i < testCases.size(); i++) { var testCase = testCases.get(i); writables.add(goTemplate(""" $testCaseDocs:W func TestEndpointCase$caseIdx:L(t *$testingT:T) { $testBody:W } """, commonCodegenArgs, MapUtils.of( "caseIdx", i, "testingT", SymbolUtils.createValueSymbolBuilder("T", SmithyGoDependency.TESTING).build(), "testCaseDocs", generateTestCaseDocs(testCase), "testBody", generateTestCase(parameters, testCase)))); } return joinWritables(writables, "\n\n"); } private GoWriter.Writable generateTestCaseDocs(EndpointTestCase testCase) { if (testCase.getDocumentation().isPresent()) { return goDocTemplate(testCase.getDocumentation().get()); } return emptyGoTemplate(); } private GoWriter.Writable generateTestCase(Parameters parameters, EndpointTestCase testCase) { return goTemplate(""" var params = $parametersType:T{ $parameterValues:W } resolver := $newResolverFn:T() result, err := resolver.$resolveEndpointMethodName:L($contextBG:T(), params) _, _ = result, err $expectErr:W $expectEndpoint:W """, commonCodegenArgs, MapUtils.of( "contextBG", SymbolUtils.createValueSymbolBuilder("Background", SmithyGoDependency.CONTEXT).build(), "parameterValues", generateParameterValues(parameters, testCase), "expectErr", generateExpectError(testCase.getExpect().getError()), "expectEndpoint", generateExpectEndpoint(testCase.getExpect().getEndpoint()))); } private GoWriter.Writable generateParameterValues(Parameters parameters, EndpointTestCase testCase) { List writables = new ArrayList<>(); // TODO filter keys based on actual modeled parameters Set parameterNames = new TreeSet<>(); parameters.forEach((p) -> { parameterNames.add(getExportedParameterName(p)); }); testCase.getParams().getMembers().forEach((key, value) -> { var exportedName = getExportedParameterName(key); if (parameterNames.contains(exportedName)) { writables.add((GoWriter w) -> { w.write("$L: $W,", getExportedParameterName(key), generateParameterValue(value)); }); } }); return joinWritables(writables, "\n"); } private GoWriter.Writable generateParameterValue(Node value) { return switch (value.getType()) { case STRING -> goTemplate("$T($S)", SmithyGoDependency.SMITHY_PTR.func("String"), value.expectStringNode().getValue()); case BOOLEAN -> goTemplate("$T($L)", SmithyGoDependency.SMITHY_PTR.func("Bool"), value.expectBooleanNode().getValue()); // only array parameter type is STRING_ARRAY case ARRAY -> writableStringSlice(value.expectArrayNode()); default -> throw new CodegenException("Unhandled member type: " + value.getType()); }; } GoWriter.Writable generateExpectError(Optional expectErr) { if (expectErr.isEmpty()) { return goTemplate(""" if err != nil { t.Fatalf("expect no error, got %v", err) } """); } return goTemplate(""" if err == nil { t.Fatalf("expect error, got none") } if e, a := $expect:S, err.Error(); !$stringsContains:T(a, e) { t.Errorf("expect %v error in %v", e, a) } """, MapUtils.of( "expect", expectErr.get(), "stringsContains", SymbolUtils.createValueSymbolBuilder("Contains", SmithyGoDependency.STRINGS).build())); } GoWriter.Writable generateExpectEndpoint(Optional expectEndpoint) { if (expectEndpoint.isEmpty()) { return emptyGoTemplate(); } var endpoint = expectEndpoint.get(); return goTemplate(""" uri, _ := $urlParse:T($expectURL:S) expectEndpoint := $endpointType:T{ URI: *uri, $headers:W $properties:W } if e, a := expectEndpoint.URI, result.URI; e != a{ t.Errorf("expect %v URI, got %v", e, a) } $assertFields:W $assertProperties:W """, commonCodegenArgs, MapUtils.of( "urlParse", SymbolUtils.createValueSymbolBuilder("Parse", SmithyGoDependency.NET_URL).build(), "expectURL", endpoint.getUrl(), "headers", generateHeaders(endpoint.getHeaders()), "properties", generateProperties(endpoint.getProperties()), "assertFields", generateAssertFields(), "assertProperties", generateAssertProperties())); } GoWriter.Writable generateHeaders(Map> headers) { Map commonArgs = MapUtils.of( "memberName", "Headers", "headerType", SymbolUtils.createPointableSymbolBuilder("Header", SmithyGoDependency.NET_HTTP).build(), "newHeaders", SymbolUtils.createValueSymbolBuilder("Header{}", SmithyGoDependency.NET_HTTP).build()); if (headers.isEmpty()) { return goTemplate("Headers: $newHeaders:T,", commonArgs); } return goBlockTemplate(""" $memberName:L: func() $headerType:T { headers := $newHeaders:T """, """ return headers }(), """, commonArgs, (w) -> { headers.forEach((key, values) -> { List valueWritables = new ArrayList<>(); values.forEach((value) -> { valueWritables.add((GoWriter ww) -> ww.write("$S", value)); }); w.writeGoTemplate("headers.Set($key:S, $values:W)", commonArgs, MapUtils.of( "key", key, "values", joinWritables(valueWritables, ", "))); }); }); } GoWriter.Writable generateAssertFields() { return goTemplate(""" if !$T(expectEndpoint.Headers, result.Headers) { t.Errorf("expect headers to match\\n%v != %v", expectEndpoint.Headers, result.Headers) } """, SmithyGoDependency.REFLECT.valueSymbol("DeepEqual")); } GoWriter.Writable generateProperties(Map properties) { Map commonArgs = MapUtils.of( "propertiesType", SymbolUtils.createValueSymbolBuilder("Properties", SmithyGoDependency.SMITHY).build()); if (properties.isEmpty()) { return goTemplate("Properties: $propertiesType:T{},", commonArgs); } var expressionGenerator = new ExpressionGenerator(Scope.empty(), name -> null); return goBlockTemplate(""" Properties: func() $propertiesType:T { var out $propertiesType:T """, """ return out }(), """, commonArgs, (w) -> { properties.forEach((key, value) -> { if (key.equals("authSchemes")) { w.write("$W", new AuthSchemePropertyGenerator(expressionGenerator) .generate(Expression.fromNode(value))); } else { w.writeGoTemplate("out.Set($key:S, $value:W)", commonArgs, MapUtils.of( "key", key, "value", generateNodeValue(value))); } }); }); } GoWriter.Writable generateNodeValue(Node value) { return value.accept(new NodeVisitor<>() { @Override public GoWriter.Writable arrayNode(ArrayNode arrayNode) { List elements = new ArrayList<>(); arrayNode.getElements().forEach((e) -> { elements.add((GoWriter w) -> w.write("$W,", e.accept(this))); }); return (GoWriter w) -> { w.write(""" []interface{}{ $W } """, joinWritables(elements, "\n")); }; } @Override public GoWriter.Writable booleanNode(BooleanNode booleanNode) { return (GoWriter w) -> w.write("$L", booleanNode.getValue()); } @Override public GoWriter.Writable nullNode(NullNode nullNode) { return (GoWriter w) -> w.write("nil"); } @Override public GoWriter.Writable numberNode(NumberNode numberNode) { return (GoWriter w) -> w.write("$L", numberNode.toString()); } @Override public GoWriter.Writable objectNode(ObjectNode objectNode) { List members = new ArrayList<>(); objectNode.getMembers().forEach((k, v) -> { members.add((GoWriter w) -> w.write("$S: $W,", k.getValue(), v.accept(this))); }); return (GoWriter w) -> { w.write(""" map[string]interface{}{ $W } """, joinWritables(members, "\n")); }; } @Override public GoWriter.Writable stringNode(StringNode stringNode) { return (GoWriter w) -> w.write("$S", stringNode.getValue()); } }); } GoWriter.Writable generateAssertProperties() { return goTemplate(""" if !$reflectDeepEqual:T(expectEndpoint.Properties, result.Properties) { t.Errorf("expect properties to match\\n%v != %v", expectEndpoint.Properties, result.Properties) } """, MapUtils.of( "reflectDeepEqual", SmithyGoDependency.REFLECT.valueSymbol("DeepEqual"), "propertiesType", SymbolUtils.createValueSymbolBuilder("Properties", SmithyGoDependency.SMITHY).build())); } public static Builder builder() { return new Builder(); } public static final class Builder implements SmithyBuilder { private Symbol newResolverFn; private Symbol parametersType; private Symbol endpointType; private String resolveEndpointMethodName; private Builder() { } public Builder endpointType(Symbol endpointType) { this.endpointType = endpointType; return this; } public Builder newResolverFn(Symbol newResolverFn) { this.newResolverFn = newResolverFn; return this; } public Builder parametersType(Symbol parametersType) { this.parametersType = parametersType; return this; } public Builder resolveEndpointMethodName(String resolveEndpointMethodName) { this.resolveEndpointMethodName = resolveEndpointMethodName; return this; } @Override public EndpointTestsGenerator build() { return new EndpointTestsGenerator(this); } } } ExpressionGenerator.java000066400000000000000000000205301463735525100403540ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import static software.amazon.smithy.go.codegen.GoWriter.goBlockTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.List; import java.util.Map; import java.util.function.Function; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.SourceLocation; import software.amazon.smithy.rulesengine.language.syntax.Identifier; import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; import software.amazon.smithy.rulesengine.language.syntax.expressions.ExpressionVisitor; import software.amazon.smithy.rulesengine.language.syntax.expressions.Reference; import software.amazon.smithy.rulesengine.language.syntax.expressions.Template; import software.amazon.smithy.rulesengine.language.syntax.expressions.TemplateVisitor; import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionDefinition; import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.GetAttr; import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.Literal; import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.LiteralVisitor; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.StringUtils; final class ExpressionGenerator { private final Scope scope; private final FnProvider fnProvider; ExpressionGenerator(Scope scope, FnProvider fnProvider) { this.scope = scope; this.fnProvider = fnProvider; } public GoWriter.Writable generate(Expression expr) { var exprOrRef = expr; var optExprIdent = scope.getIdent(expr); if (optExprIdent.isPresent()) { exprOrRef = new Reference(Identifier.of(optExprIdent.get()), SourceLocation.NONE); } return exprOrRef.accept(new ExpressionGeneratorVisitor(scope, fnProvider)); } private record ExpressionGeneratorVisitor(Scope scope, FnProvider fnProvider) implements ExpressionVisitor { @Override public GoWriter.Writable visitLiteral(Literal literal) { return literal.accept(new LiteralGeneratorVisitor(scope, fnProvider)); } @Override public GoWriter.Writable visitRef(Reference ref) { return goTemplate(ref.getName().toString()); } @Override public GoWriter.Writable visitGetAttr(GetAttr getAttr) { var target = new ExpressionGenerator(scope, fnProvider).generate(getAttr.getTarget()); var path = (GoWriter.Writable) (GoWriter w) -> { getAttr.getPath().stream().toList().forEach((part) -> { if (part instanceof GetAttr.Part.Key) { w.writeInline(".$L", getBuiltinMemberName(((GetAttr.Part.Key) part).key())); } else if (part instanceof GetAttr.Part.Index) { w.writeInline(".Get($L)", ((GetAttr.Part.Index) part).index()); } }); }; return (GoWriter w) -> w.writeInline("$W$W", target, path); } @Override public GoWriter.Writable visitIsSet(Expression expr) { return (GoWriter w) -> { w.write("$W != nil", new ExpressionGenerator(scope, fnProvider).generate(expr)); }; } @Override public GoWriter.Writable visitNot(Expression expr) { return (GoWriter w) -> { w.write("!($W)", new ExpressionGenerator(scope, fnProvider).generate(expr)); }; } @Override public GoWriter.Writable visitBoolEquals(Expression left, Expression right) { return (GoWriter w) -> { var generator = new ExpressionGenerator(scope, fnProvider); w.write("$W == $W", generator.generate(left), generator.generate(right)); }; } @Override public GoWriter.Writable visitStringEquals(Expression left, Expression right) { return (GoWriter w) -> { var generator = new ExpressionGenerator(scope, fnProvider); w.write("$W == $W", generator.generate(left), generator.generate(right)); }; } @Override public GoWriter.Writable visitLibraryFunction(FunctionDefinition fnDef, List args) { return new FnGenerator(scope, fnProvider).generate(fnDef, args); } } private record LiteralGeneratorVisitor(Scope scope, FnProvider fnProvider) implements LiteralVisitor { @Override public GoWriter.Writable visitBoolean(boolean b) { return goTemplate(String.valueOf(b)); } @Override public GoWriter.Writable visitString(Template value) { var parts = value.accept( new TemplateGeneratorVisitor((expr) -> new ExpressionGenerator(scope, fnProvider).generate(expr)) ).toList(); return (GoWriter w) -> { parts.forEach((p) -> w.write("$W", p)); }; } @Override public GoWriter.Writable visitRecord(Map members) { return goBlockTemplate("map[string]interface{}{", "}", (w) -> { members.forEach((k, v) -> { w.write("$S: $W,", k.getName().toString(), new ExpressionGenerator(scope, fnProvider).generate(v)); }); }); } @Override public GoWriter.Writable visitTuple(List members) { return goBlockTemplate("[]interface{}{", "}", (w) -> { members.forEach((v) -> w.write("$W,", new ExpressionGenerator(scope, fnProvider).generate(v))); }); } @Override public GoWriter.Writable visitInteger(int i) { return goTemplate(String.valueOf(i)); } } private record TemplateGeneratorVisitor( Function generator) implements TemplateVisitor { @Override public GoWriter.Writable visitStaticTemplate(String s) { return (GoWriter w) -> w.write("$S", s); } @Override public GoWriter.Writable visitSingleDynamicTemplate(Expression expr) { return this.generator.apply(expr); } @Override public GoWriter.Writable visitStaticElement(String s) { return (GoWriter w) -> { w.write("out.WriteString($S)", s); }; } @Override public GoWriter.Writable visitDynamicElement(Expression expr) { return (GoWriter w) -> { w.write("out.WriteString($W)", this.generator.apply(expr)); }; } @Override public GoWriter.Writable startMultipartTemplate() { return goTemplate(""" func() string { var out $stringsBuilder:T """, MapUtils.of( "stringsBuilder", SymbolUtils.createValueSymbolBuilder("Builder", SmithyGoDependency.STRINGS).build())); } @Override public GoWriter.Writable finishMultipartTemplate() { return goTemplate(""" return out.String() }() """); } } private static String getBuiltinMemberName(Identifier ident) { return StringUtils.capitalize(ident.getName().toString()); } } FnGenerator.java000066400000000000000000000065711463735525100365710ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.GoWriter.joinWritables; import java.util.ArrayList; import java.util.List; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionDefinition; import software.amazon.smithy.utils.MapUtils; public class FnGenerator { private final Scope scope; private final FnProvider fnProvider; public FnGenerator(Scope scope, FnProvider fnProvider) { this.scope = scope; this.fnProvider = fnProvider; } GoWriter.Writable generate(FunctionDefinition fnDef, List fnArgs) { Symbol goFn; if (this.fnProvider.fnFor(fnDef.getId()) == null) { var defaultFnProvider = new DefaultFnProvider(); goFn = defaultFnProvider.fnFor(fnDef.getId()); } else { goFn = this.fnProvider.fnFor(fnDef.getId()); } List writableFnArgs = new ArrayList<>(); fnArgs.forEach((expr) -> { writableFnArgs.add(new ExpressionGenerator(scope, this.fnProvider).generate(expr)); }); return goTemplate("$fn:T($args:W)", MapUtils.of( "fn", goFn, "args", joinWritables(writableFnArgs, ", "))); } public static class DefaultFnProvider implements FnProvider { @Override public Symbol fnFor(String name) { return switch (name) { case "isValidHostLabel" -> SymbolUtils.createValueSymbolBuilder("IsValidHostLabel", SmithyGoDependency.SMITHY_ENDPOINT_RULESFN).build(); case "parseURL" -> SymbolUtils.createValueSymbolBuilder("ParseURL", SmithyGoDependency.SMITHY_ENDPOINT_RULESFN).build(); case "substring" -> SymbolUtils.createValueSymbolBuilder("SubString", SmithyGoDependency.SMITHY_ENDPOINT_RULESFN).build(); case "uriEncode" -> SymbolUtils.createValueSymbolBuilder("URIEncode", SmithyGoDependency.SMITHY_ENDPOINT_RULESFN).build(); default -> null; }; } } static boolean isFnResultOptional(FunctionDefinition fn) { return switch (fn.getId()) { case "isValidHostLabel" -> true; case "parseURL" -> true; case "substring" -> true; case "uriEncode" -> false; default -> false; }; } } FnProvider.java000066400000000000000000000016011463735525100364220ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import software.amazon.smithy.codegen.core.Symbol; /* * FnProvider is the means by which generator consumers * can provide custom library functions to be invoked by * by Endpoint resolution. */ public interface FnProvider { Symbol fnFor(String name); } Scope.java000066400000000000000000000032431463735525100354210ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.endpoints; import java.util.HashMap; import java.util.Map; import java.util.Optional; import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; /* * Provides contextualized scope down the call tree to inform generator of expression origin. */ class Scope { private final Map mapping; Scope(Map mapping) { this.mapping = mapping; } static Scope empty() { return new Scope(new HashMap<>()); } Optional getIdent(Expression expr) { if (!mapping.containsKey(expr)) { return Optional.empty(); } return Optional.of(mapping.get(expr)); } Scope withMember(Expression expr, String name) { Map newMapping = new HashMap<>(mapping); newMapping.put(expr, name); return new Scope(newMapping); } Scope withIdent(Expression expr, String name) { var newMapping = new HashMap<>(mapping); newMapping.put(expr, name); return new Scope(newMapping); } } integration/000077500000000000000000000000001463735525100340235ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegenAddChecksumRequiredMiddleware.java000066400000000000000000000041561463735525100425460ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.List; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; 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.traits.HttpChecksumRequiredTrait; import software.amazon.smithy.utils.ListUtils; /** * Adds middleware supporting httpChecksumRequired trait behavior. */ public class AddChecksumRequiredMiddleware implements GoIntegration { @Override public byte getOrder() { return 0; } @Override public List getClientPlugins() { return ListUtils.of( RuntimeClientPlugin.builder() .operationPredicate(this::hasChecksumRequiredTrait) .registerMiddleware(MiddlewareRegistrar.builder() .resolvedFunction(SymbolUtils.createValueSymbolBuilder( "AddContentChecksumMiddleware", SmithyGoDependency.SMITHY_HTTP_TRANSPORT).build()) .build()) .build() ); } // return true if operation shape is decorated with `httpChecksumRequired` trait. private boolean hasChecksumRequiredTrait(Model model, ServiceShape service, OperationShape operation) { return operation.hasTrait(HttpChecksumRequiredTrait.class); } } AuthSchemeDefinition.java000066400000000000000000000037561463735525100407400ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. * * */ package software.amazon.smithy.go.codegen.integration; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; /** * Defines code generation for a modeled auth scheme. */ public interface AuthSchemeDefinition { /** * Generates the service default option for this scheme. */ GoWriter.Writable generateServiceOption(ProtocolGenerator.GenerationContext context, ServiceShape service); /** * Generates an operation-specific option for this scheme. This will only be called when the generator encounters * an operation with auth overrides. */ GoWriter.Writable generateOperationOption(ProtocolGenerator.GenerationContext context, OperationShape operation); /** * Generates a default auth scheme. Called within a context where client Options are available. */ default GoWriter.Writable generateDefaultAuthScheme() { return emptyGoTemplate(); } /** * Generates the value to return from Options.GetIdentityResolver(schemeID). Called within a context where client * Options are available. */ default GoWriter.Writable generateOptionsIdentityResolver() { return goTemplate("nil"); } } ClientLogger.java000066400000000000000000000101131463735525100372400ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.List; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.Model; import software.amazon.smithy.utils.ListUtils; /** * Adds a client logger to the client. */ public class ClientLogger implements GoIntegration { private static final String DEFAULT_LOGGER_RESOLVER = "resolveDefaultLogger"; private static final String LOGGER_CONFIG_NAME = "Logger"; private static final String REGISTER_MIDDLEWARE = "addSetLoggerMiddleware"; @Override public byte getOrder() { return -127; } @Override public void writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator goDelegator ) { goDelegator.useShapeWriter(settings.getService(model), writer -> { writer.openBlock("func $L(o *Options) {", "}", DEFAULT_LOGGER_RESOLVER, () -> { Symbol nopSymbol = SymbolUtils.createValueSymbolBuilder("Nop", SmithyGoDependency.SMITHY_LOGGING) .build(); writer.openBlock("if o.$L != nil {", "}", LOGGER_CONFIG_NAME, () -> { writer.write("return"); }); writer.write("o.$L = $T{}", LOGGER_CONFIG_NAME, nopSymbol); }); writer.write(""); Symbol stackSymbol = SymbolUtils.createPointableSymbolBuilder("Stack", SmithyGoDependency.SMITHY_MIDDLEWARE) .build(); Symbol helperSymbol = SymbolUtils.createValueSymbolBuilder("AddSetLoggerMiddleware", SmithyGoDependency.SMITHY_MIDDLEWARE).build(); writer.openBlock("func $L(stack $P, o Options) error {", "}", REGISTER_MIDDLEWARE, stackSymbol, () -> { writer.write("return $T(stack, o.$L)", helperSymbol, LOGGER_CONFIG_NAME); }); writer.write(""); }); } @Override public List getClientPlugins() { return ListUtils.of( RuntimeClientPlugin.builder() .addConfigField(ConfigField.builder() .name(LOGGER_CONFIG_NAME) .type(SymbolUtils.createValueSymbolBuilder("Logger", SmithyGoDependency.SMITHY_LOGGING) .build()) .documentation("The logger writer interface to write logging messages to.") .build()) .addConfigFieldResolver(ConfigFieldResolver.builder() .location(ConfigFieldResolver.Location.CLIENT) .target(ConfigFieldResolver.Target.INITIALIZATION) .resolver(SymbolUtils.createValueSymbolBuilder(DEFAULT_LOGGER_RESOLVER).build()) .build()) .registerMiddleware(MiddlewareRegistrar.builder() .resolvedFunction(SymbolUtils.createValueSymbolBuilder(REGISTER_MIDDLEWARE).build()) .useClientOptions() .build()) .build() ); } } ClientMember.java000066400000000000000000000074021463735525100372370ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.Objects; import java.util.Optional; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.ToSmithyBuilder; /** * Represents a member field on a client struct. */ public class ClientMember implements ToSmithyBuilder { private final String name; private final Symbol type; private final String documentation; public ClientMember(Builder builder) { this.name = Objects.requireNonNull(builder.name); this.type = Objects.requireNonNull(builder.type); this.documentation = builder.documentation; } /** * @return Returns the name of the client member field. */ public String getName() { return name; } /** * @return Returns the type Symbol for the member field. */ public Symbol getType() { return type; } /** * @return Gets the optional documentation for the member field. */ public Optional getDocumentation() { return Optional.ofNullable(documentation); } @Override public SmithyBuilder toBuilder() { return builder().type(type).name(name).documentation(documentation); } public static Builder builder() { return new Builder(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ClientMember that = (ClientMember) o; return Objects.equals(getName(), that.getName()) && Objects.equals(getType(), that.getType()) && Objects.equals(getDocumentation(), that.getDocumentation()); } @Override public int hashCode() { return Objects.hash(getName(), getType(), getDocumentation()); } /** * Builds a ClientMember. */ public static class Builder implements SmithyBuilder { private String name; private Symbol type; private String documentation; @Override public ClientMember build() { return new ClientMember(this); } /** * Set the name of the member field on client. * * @param name is the name of the field on the client. * @return Returns the builder. */ public Builder name(String name) { this.name = name; return this; } /** * Sets the type of the client field. * * @param type A Symbol representing the type of the client field. * @return Returns the builder. */ public Builder type(Symbol type) { this.type = type; return this; } /** * Sets the documentation for the client field. * * @param documentation The documentation for the client field. * @return Returns the builder. */ public Builder documentation(String documentation) { this.documentation = documentation; return this; } } } ClientMemberResolver.java000066400000000000000000000044031463735525100407570ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.Objects; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.utils.SmithyBuilder; /** * Represent symbol that points to a function that operates * on the client member fields during client construction. * * Any configuration that a plugin requires in order to function should be * checked in this function, either setting a default value if possible or * returning an error if not. */ public final class ClientMemberResolver { private final Symbol resolver; private ClientMemberResolver(Builder builder) { resolver = SmithyBuilder.requiredState("resolver", builder.resolver); } public Symbol getResolver() { return resolver; } public static Builder builder() { return new Builder(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ClientMemberResolver that = (ClientMemberResolver) o; return resolver.equals(that.resolver); } /** * Returns a hash code value for the object. * @return the hash code. */ @Override public int hashCode() { return Objects.hash(resolver); } public static class Builder implements SmithyBuilder { private Symbol resolver; public Builder resolver(Symbol resolver) { this.resolver = resolver; return this; } @Override public ClientMemberResolver build() { return new ClientMemberResolver(this); } } } ConfigField.java000066400000000000000000000130371463735525100370430ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.Objects; import java.util.Optional; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.ToSmithyBuilder; /** * Represents a config field on a client config struct. */ public class ConfigField implements ToSmithyBuilder { private final String name; private final Symbol type; private final String documentation; private final String deprecated; private final Boolean withHelper; public ConfigField(Builder builder) { this.name = Objects.requireNonNull(builder.name); this.type = Objects.requireNonNull(builder.type); this.documentation = builder.documentation; this.deprecated = builder.deprecated; this.withHelper = builder.withHelper; } /** * @return Returns the name of the config field. */ public String getName() { return name; } /** * @return Returns the type Symbol for the field. */ public Symbol getType() { return type; } /** * @return Returns if the config option should have a with helper or not. */ public Boolean getWithHelper() { return withHelper; } /** * @return Gets the optional documentation for the field. */ public Optional getDocumentation() { return Optional.ofNullable(documentation); } /** * @return The optional deprecation documentation for the field. */ public Optional getDeprecated() { return Optional.ofNullable(deprecated); } /** * @return Returns if the config option is deprecated. */ public Boolean isDeprecated() { return getDeprecated().isPresent(); } @Override public SmithyBuilder toBuilder() { return builder().type(type).name(name).documentation(documentation).withHelper(withHelper); } public static Builder builder() { return new Builder(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ConfigField that = (ConfigField) o; return Objects.equals(getName(), that.getName()) && Objects.equals(getType(), that.getType()) && Objects.equals(getDocumentation(), that.getDocumentation()) && Objects.equals(getWithHelper(), that.getWithHelper()); } @Override public int hashCode() { return Objects.hash(getName(), getType(), getDocumentation(), getWithHelper()); } /** * Builds a ConfigField. */ public static class Builder implements SmithyBuilder { private String name; private Symbol type; private String documentation; private String deprecated; private Boolean withHelper = false; @Override public ConfigField build() { return new ConfigField(this); } /** * Set the name of the config field. * * @param name the name of the config field. * @return Returns the builder. */ public Builder name(String name) { this.name = name; return this; } /** * Sets the type of the config field. * * @param type A Symbol representing the type of the config field. * @return Returns the builder. */ public Builder type(Symbol type) { this.type = type; return this; } /** * Sets the documentation for the config field. * * @param documentation The documentation for the config field. * @return Returns the builder. */ public Builder documentation(String documentation) { this.documentation = documentation; return this; } /** * Sets the configuration field as deprecated with the provided message. * * @param deprecated The deprecation message for the config field. * @return Returns the builder. */ public Builder deprecated(String deprecated) { this.deprecated = deprecated; return this; } /** * Sets if the client include a With__ helper for this config option. * * @param withHelper if with helper should be generated or not. * @return Returns the builder. */ public Builder withHelper(Boolean withHelper) { this.withHelper = withHelper; return this; } /** * Sets that the client will include a With__ helper for the client option. * * @return Returns the builder. */ public Builder withHelper() { this.withHelper = true; return this; } } } ConfigFieldResolver.java000066400000000000000000000121441463735525100405630ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.Objects; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.utils.SmithyBuilder; /** * Represent symbol that points to a function that operates * on the client options during client construction or operation invocation. *

* Can target options prior customer mutation (Initialization), or after customer mutation (Finalization). *

* Any configuration that a plugin requires in order to function should be * checked in this function, either setting a default value if possible or * returning an error if not. */ public final class ConfigFieldResolver { private final Location location; private final Target target; private final Symbol resolver; private final boolean withOperationName; private final boolean withClientInput; private ConfigFieldResolver(Builder builder) { location = SmithyBuilder.requiredState("location", builder.location); target = SmithyBuilder.requiredState("target", builder.target); resolver = SmithyBuilder.requiredState("resolver", builder.resolver); withOperationName = builder.withOperationName; withClientInput = builder.withClientInput; } public Location getLocation() { return location; } public Target getTarget() { return target; } public Symbol getResolver() { return resolver; } public boolean isWithOperationName() { return withOperationName && location == Location.OPERATION; } public boolean isWithClientInput() { return withClientInput; } public static Builder builder() { return new Builder(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ConfigFieldResolver that = (ConfigFieldResolver) o; return location == that.location && target == that.target && resolver.equals(that.resolver) && withOperationName == that.withOperationName && withClientInput == that.withClientInput; } /** * Returns a hash code value for the object. * * @return the hash code. */ @Override public int hashCode() { return Objects.hash(location, target, resolver); } /** * The location where the resolver is executed. */ public enum Location { /** * Indicates that the resolver is executed in the client constructor. */ CLIENT, /** * Indicates that the resolver is executed during operation invocation. */ OPERATION } /** * Indicates the target of the resolver. */ public enum Target { /** * Indicates that the resolver targets config fields prior to customer mutation. */ INITIALIZATION, /** * Indicates that the resolver targets config fields after customer mutation. */ FINALIZATION, /** * Indicates that the resolver targets config fields after the client has been instantiated. Resolvers with this * target can then take a reference to the instantiated client in existing Options, but CANNOT modify fields on * Options since it is passed to the already-existing client by value. */ FINALIZATION_WITH_CLIENT } public static class Builder implements SmithyBuilder { private Location location; private Target target; private Symbol resolver; private boolean withOperationName = false; private boolean withClientInput = false; public Builder location(Location location) { this.location = location; return this; } public Builder target(Target target) { this.target = target; return this; } public Builder resolver(Symbol resolver) { this.resolver = resolver; return this; } public Builder withOperationName(boolean withOperationName) { this.withOperationName = withOperationName; return this; } public Builder withClientInput(boolean withClientInput) { this.withClientInput = withClientInput; return this; } @Override public ConfigFieldResolver build() { return new ConfigFieldResolver(this); } } } DefaultProtocols.java000066400000000000000000000017451463735525100401660ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.List; import software.amazon.smithy.go.codegen.protocol.rpc2.cbor.Rpc2CborProtocolGenerator; import software.amazon.smithy.utils.ListUtils; public class DefaultProtocols implements GoIntegration { @Override public List getProtocolGenerators() { return ListUtils.of(new Rpc2CborProtocolGenerator()); } } DocumentShapeDeserVisitor.java000066400000000000000000000460471463735525100420030ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.Collections; import java.util.Map; import java.util.function.BiConsumer; import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator.GenerationContext; import software.amazon.smithy.model.shapes.CollectionShape; import software.amazon.smithy.model.shapes.DocumentShape; import software.amazon.smithy.model.shapes.ListShape; import software.amazon.smithy.model.shapes.MapShape; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ResourceShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.SetShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeVisitor; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.UnionShape; /** * Visitor to generate deserialization functions for shapes in protocol document bodies. *

* Visitor methods for aggregate types except maps and collections are final and will * generate functions that dispatch their loading from the body to the matching abstract method. *

* Visitor methods for all other types will default to not generating deserialization * functions. This may be overwritten by downstream implementations if the protocol requires * more complex deserialization strategies for those types. *

* The standard implementation is as follows; no assumptions are made about the protocol * being generated for. * *

    *
  • Service, Operation, Resource: no function generated. Not overridable.
  • *
  • Document, List, Map, Set, Structure, Union: generates a deserialization function. * Not overridable.
  • *
  • All other types: no function generated. May be overridden.
  • *
*/ public abstract class DocumentShapeDeserVisitor extends ShapeVisitor.Default { public interface DeserializerNameProvider { String getName(Shape shape, ServiceShape service, String protocol); } private final GenerationContext context; private final DeserializerNameProvider deserializerNameProvider; public DocumentShapeDeserVisitor(GenerationContext context) { this(context, null); } public DocumentShapeDeserVisitor(GenerationContext context, DeserializerNameProvider deserializerNameProvider) { this.context = context; this.deserializerNameProvider = deserializerNameProvider; } /** * Gets the generation context. * * @return The generation context. */ protected final GenerationContext getContext() { return context; } @Override protected Void getDefault(Shape shape) { return null; } /** * Writes the code needed to deserialize a collection in the document of a response. * *

Implementations of this method are expected to generate a function body that * returns the type generated for the CollectionShape {@code shape} parameter. * *

For example, given the following Smithy model: * *

{@code
     * list ParameterList {
     *     member: Parameter
     * }
     * }
* *

The function signature for this body will return only {@code error} and have at * least one parameter in scope: *

    *
  • {@code v *[]*string}: a pointer to the location the resulting list should be deserialized to.
  • *
* *

It will also have any parameters in scope as defined by {@code getAdditionalArguments}. * For json, for instance, you may want to pass around a {@code *json.Decoder} to handle parsing * the json. * *

The function could end up looking like: * *

{@code
     * func myProtocol_deserializeDocumentParameterList(v *[]*types.Parameter, decoder *json.Decoder) error {
     *     if v == nil {
     *         return fmt.Errorf("unexpected nil of type %T", v)
     *     }
     *     startToken, err := decoder.Token()
     *     if err == io.EOF {
     *         return nil
     *     }
     *     if err != nil {
     *         return err
     *     }
     *     if startToken == nil {
     *         return nil
     *     }
     *     if t, ok := startToken.(json.Delim); !ok || t != '[' {
     *         return fmt.Errorf("expect `[` as start token")
     *     }
     *
     *     var cv []*types.Parameter
     *     if *v == nil {
     *         cv = []*types.Parameter{}
     *     } else {
     *         cv = *v
     *     }
     *
     *     for decoder.More() {
     *         var col *types.Parameter
     *         if err := myProtocol_deserializeDocumentParameter(&col, decoder); err != nil {
     *             return err
     *         }
     *         cv = append(cv, col)
     *     }
     *
     *     endToken, err := decoder.Token()
     *     if err != nil {
     *         return err
     *     }
     *     if t, ok := endToken.(json.Delim); !ok || t != ']' {
     *         return fmt.Errorf("expect `]` as end token")
     *     }
     *
     *     *v = cv
     *     return nil
     * }
     * }
* * @param context The generation context. * @param shape The collection shape being generated. */ protected abstract void deserializeCollection(GenerationContext context, CollectionShape shape); /** * Writes the code needed to deserialize a document shape in the document of a response. * *

Implementations of this method are expected to generate a function body that * returns the type generated for the DocumentShape {@code shape} parameter. * *

For example, given the following Smithy model: * *

{@code
     * document FooDocument
     * }
* *

The function signature for this body will return only {@code error} and have at * least one parameter in scope: *

    *
  • {@code v *Document}: a pointer to the location the resulting document should be deserialized to.
  • *
* *

It will also have any parameters in scope as defined by {@code getAdditionalArguments}. * For json, for instance, you may want to pass around a {@code *json.Decoder} to handle parsing * the json. * * @param context The generation context. * @param shape The document shape being generated. */ protected abstract void deserializeDocument(GenerationContext context, DocumentShape shape); /** * Writes the code needed to deserialize a map in the document of a response. * *

Implementations of this method are expected to generate a function body that * returns the type generated for the MapShape {@code shape} parameter. * *

For example, given the following Smithy model: * *

{@code
     * map c {
     *     key: String,
     *     value: Field
     * }
     * }
* *

The function signature for this body will return only {@code error} and have at * least one parameter in scope: *

    *
  • {@code v *map[string]*types.FieldMap}: a pointer to the location the resulting map should * be deserialized to.
  • *
* *

It will also have any parameters in scope as defined by {@code getAdditionalArguments}. * For json, for instance, you may want to pass around a {@code *json.Decoder} to handle parsing * the json. * *

The function could end up looking like: * *

{@code
     * func myProtocol_deserializeDocumentFieldMap(v *map[string]*types.FieldMap, decoder *json.Decoder) error {
     *     if v == nil {
     *         return fmt.Errorf("unexpected nil of type %T", v)
     *     }
     *     startToken, err := decoder.Token()
     *     if err == io.EOF {
     *         return nil
     *     }
     *     if err != nil {
     *         return err
     *     }
     *     if startToken == nil {
     *         return nil
     *     }
     *     if t, ok := startToken.(json.Delim); !ok || t != '{' {
     *         return fmt.Errorf("expect `{` as start token")
     *     }
     *
     *     var mv map[string]*types.FieldMap
     *     if *v == nil {
     *         mv = map[string]*types.FieldMap{}
     *     } else {
     *         mv = *v
     *     }
     *
     *     for decoder.More() {
     *         token, err := decoder.Token()
     *         if err != nil {
     *             return err
     *         }
     *
     *         key, ok := token.(string)
     *         if !ok {
     *             return fmt.Errorf("expected map-key of type string, found type %T", token)
     *         }
     *
     *         var parsedVal *types.FieldMap
     *         if err := myProtocol_deserializeDocumentFieldMap(&parsedVal, decoder); err != nil {
     *             return err
     *         }
     *         mv[key] = parsedVal
     *
     *     }
     *     endToken, err := decoder.Token()
     *     if err != nil {
     *         return err
     *     }
     *     if t, ok := endToken.(json.Delim); !ok || t != '}' {
     *         return fmt.Errorf("expect `}` as end token")
     *     }
     *
     *     *v = mv
     *     return nil
     * }
     * }
* * @param context The generation context. * @param shape The map shape being generated. */ protected abstract void deserializeMap(GenerationContext context, MapShape shape); /** * Writes the code needed to deserialize a structure in the document of a response. * *

Implementations of this method are expected to generate a function body that * returns the type generated for the StructureShape {@code shape} parameter. * *

For example, given the following Smithy model: * *

{@code
     * structure Field {
     *     FooValue: Foo,
     *     BarValue: String,
     * }
     * }
* *

The function signature for this body will return only {@code error} and have at * least one parameter in scope: *

    *
  • {@code v **types.Field}: a pointer to the location the resulting structure should be deserialized to.
  • *
* *

It will also have any parameters in scope as defined by {@code getAdditionalArguments}. * For json, for instance, you may want to pass around a {@code *json.Decoder} to handle parsing * the json. * *

The function could end up looking like: * *

{@code
     * func myProtocol_deserializeDocumentField(v **types.Field, decoder *json.Decoder) error {
     *     if v == nil {
     *         return fmt.Errorf("unexpected nil of type %T", v)
     *     }
     *     startToken, err := decoder.Token()
     *     if err == io.EOF {
     *         return nil
     *     }
     *     if err != nil {
     *         return err
     *     }
     *     if startToken == nil {
     *         return nil
     *     }
     *     if t, ok := startToken.(json.Delim); !ok || t != '{' {
     *         return fmt.Errorf("expect `{` as start token")
     *     }
     *
     *     var sv *types.KitchenSink
     *     if *v == nil {
     *         sv = &types.KitchenSink{}
     *     } else {
     *         sv = *v
     *     }
     *
     *     for decoder.More() {
     *         t, err := decoder.Token()
     *         if err != nil {
     *             return err
     *         }
     *         switch t {
     *         case "FooValue":
     *             if err := myProtocol_deserializeDocumentFoo(&sv.FooValue, decoder); err != nil {
     *                 return err
     *             }
     *         case "BarValue":
     *             val, err := decoder.Token()
     *             if err != nil {
     *                 return err
     *             }
     *             if val != nil {
     *                 jtv, ok := val.(string)
     *                 if !ok {
     *                     return fmt.Errorf("expected String to be of type string, got %T instead", val)
     *                 }
     *                 sv.BarValue = &jtv
     *             }
     *         default:
     *             // Discard the unknown
     *         }
     *     }
     *     endToken, err := decoder.Token()
     *     if err != nil {
     *         return err
     *     }
     *     if t, ok := endToken.(json.Delim); !ok || t != '}' {
     *         return fmt.Errorf("expect `}` as end token")
     *     }
     *     *v = sv
     *     return nil
     * }
     * }
* * @param context The generation context. * @param shape The structure shape being generated. */ protected abstract void deserializeStructure(GenerationContext context, StructureShape shape); /** * Writes the code needed to deserialize a union in the document of a response. * *

Implementations of this method are expected to generate a function body that * returns the type generated for the UnionShape {@code shape} parameter. * *

{@code
     * union Field {
     *     fooValue: Foo,
     *     barValue: String,
     * }
     * }
* *

The function signature for this body will return only {@code error} and have at * least one parameter in scope: *

    *
  • {@code v *Field}: a pointer to the location the resulting union should be deserialized to.
  • *
* *

It will also have any parameters in scope as defined by {@code getAdditionalArguments}. * For json, for instance, you may want to pass around a {@code *json.Decoder} to handle parsing * the json. * * @param context The generation context. * @param shape The union shape being generated. */ protected abstract void deserializeUnion(GenerationContext context, UnionShape shape); /** * Generates a function for deserializing the output shape, dispatching body handling * to the supplied function. * * @param shape The shape to generate a deserializer for. * @param functionBody An implementation that will generate a function body to * deserialize the shape. */ protected final void generateDeserFunction( Shape shape, BiConsumer functionBody ) { SymbolProvider symbolProvider = context.getSymbolProvider(); GoWriter writer = context.getWriter().get(); Symbol symbol = symbolProvider.toSymbol(shape); final String functionName; if (this.deserializerNameProvider != null) { functionName = deserializerNameProvider.getName(shape, context.getService(), context.getProtocolName()); } else { functionName = ProtocolGenerator.getDocumentDeserializerFunctionName( shape, context.getService(), context.getProtocolName()); } String additionalArguments = getAdditionalArguments().entrySet().stream() .map(entry -> String.format(", %s %s", entry.getKey(), entry.getValue())) .collect(Collectors.joining()); writer.openBlock("func $L(v *$P$L) error {", "}", functionName, symbol, additionalArguments, () -> { writer.addUseImports(SmithyGoDependency.FMT); writer.openBlock("if v == nil {", "}", () -> { writer.write("return fmt.Errorf(\"unexpected nil of type %T\", v)"); }); functionBody.accept(context, shape); }).write(""); } /** * Gets any additional arguments needed for every deserializer function. * * @return a map of argument name to argument type. */ protected Map getAdditionalArguments() { return Collections.emptyMap(); } @Override public final Void operationShape(OperationShape shape) { throw new CodegenException("Operation shapes cannot be bound to documents."); } @Override public final Void resourceShape(ResourceShape shape) { throw new CodegenException("Resource shapes cannot be bound to documents."); } @Override public final Void serviceShape(ServiceShape shape) { throw new CodegenException("Service shapes cannot be bound to documents."); } /** * Dispatches to create the body of document shape deserilization functions. * * @param shape The document shape to generate deserialization for. * @return null */ @Override public final Void documentShape(DocumentShape shape) { generateDeserFunction(shape, (c, s) -> deserializeDocument(c, s.asDocumentShape().get())); return null; } /** * Dispatches to create the body of list shape deserilization functions. * * @param shape The list shape to generate deserialization for. * @return null */ @Override public Void listShape(ListShape shape) { generateDeserFunction(shape, (c, s) -> deserializeCollection(c, s.asListShape().get())); return null; } /** * Dispatches to create the body of map shape deserilization functions. * * @param shape The map shape to generate deserialization for. * @return null */ @Override public Void mapShape(MapShape shape) { generateDeserFunction(shape, (c, s) -> deserializeMap(c, s.asMapShape().get())); return null; } /** * Dispatches to create the body of set shape deserilization functions. * * @param shape The set shape to generate deserialization for. * @return null */ @Override public Void setShape(SetShape shape) { generateDeserFunction(shape, (c, s) -> deserializeCollection(c, s.asSetShape().get())); return null; } /** * Dispatches to create the body of structure shape deserilization functions. * * @param shape The structure shape to generate deserialization for. * @return null */ @Override public final Void structureShape(StructureShape shape) { generateDeserFunction(shape, (c, s) -> deserializeStructure(c, s.asStructureShape().get())); return null; } /** * Dispatches to create the body of union shape deserilization functions. * * @param shape The union shape to generate deserialization for. * @return null */ @Override public final Void unionShape(UnionShape shape) { generateDeserFunction(shape, (c, s) -> deserializeUnion(c, s.asUnionShape().get())); return null; } } DocumentShapeSerVisitor.java000066400000000000000000000401541463735525100414630ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.Collections; import java.util.Map; import java.util.function.BiConsumer; import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator.GenerationContext; import software.amazon.smithy.model.shapes.CollectionShape; import software.amazon.smithy.model.shapes.DocumentShape; import software.amazon.smithy.model.shapes.ListShape; import software.amazon.smithy.model.shapes.MapShape; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ResourceShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.SetShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeVisitor; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.UnionShape; /** * Visitor to generate serialization for shapes in protocol document bodies. *

* Visitor methods for aggregate types are final and will generate functions that dispatch * their body generation to the matching abstract method. *

* Visitor methods for all other types will default to not generating serialization functions. * This may be overwritten by downstream implementations if the protocol requires a more * complex serialization strategy for those types. *

* The standard implementation is as follows; no assumptions are made about the protocol * being generated for. * *

    *
  • Service, Operation, Resource: no function generated. Not overridable.
  • *
  • Document, List, Map, Set, Structure, Union: generates a serialization function. * Not overridable.
  • *
  • All other types: no function generated. May be overridden.
  • *
*/ public abstract class DocumentShapeSerVisitor extends ShapeVisitor.Default { public interface SerializerNameProvider { String getName(Shape shape, ServiceShape service, String protocol); } private final GenerationContext context; private final SerializerNameProvider serializerNameProvider; public DocumentShapeSerVisitor(GenerationContext context) { this(context, null); } public DocumentShapeSerVisitor(GenerationContext context, SerializerNameProvider serializerNameProvider) { this.context = context; this.serializerNameProvider = serializerNameProvider; } /** * Gets the generation context. * * @return The generation context. */ protected final GenerationContext getContext() { return context; } /** * Writes the code needed to serialize a collection in the document of a request. * *

Implementations of this method are expected to generate a function body that * returns a value representing the CollectionShape {@code shape} parameter. * *

For example, given the following Smithy model: * *

{@code
     * list ParameterList {
     *     member: Parameter
     * }
     * }
* *

The function signature for this body will return only {@code error} and have at * least one parameter in scope: *

    *
  • {@code v []*types.Parameter}: the list to be serialized.
  • *
* *

It will also have any parameters in scope as defined by {@code getAdditionalArguments}. * For json protocols, for instance, smithy has the Value accumulator that can be passed * around to build up the json. * *

The function could end up looking like: * *

{@code
     * func myProtocol_serializeDocumentParameterList(v []*types.Parameter, value smithyjson.Value) error {
     *     array := value.Array()
     *     defer array.Close()
     *
     *     for i := range v {
     *         av := array.Value()
     *         if vv := v[i]; vv == nil {
     *             av.Null()
     *             continue
     *         }
     *         if err := myProtocol_serializeDocumentParameter(v[i], av); err != nil {
     *             return err
     *         }
     *     }
     *     return nil
     * }
     * }
* * @param context The generation context. * @param shape The collection shape being generated. */ protected abstract void serializeCollection(GenerationContext context, CollectionShape shape); /** * Writes the code needed to serialize a document shape in the document of a request. * *

Implementations of this method are expected to generate a function body that * returns a value representing the DocumentShape {@code shape} parameter. * *

For example, given the following Smithy model: * *

{@code
     * document FooDocument
     * }
* *

The function signature for this body will return only {@code error} and have at * least one parameter in scope: *

    *
  • {@code v Document}: the document to be serialized.
  • *
* *

It will also have any parameters in scope as defined by {@code getAdditionalArguments}. * For json protocols, for instance, smithy has the Value accumulator that can be passed * around to build up the json. * * @param context The generation context. * @param shape The document shape being generated. */ protected abstract void serializeDocument(GenerationContext context, DocumentShape shape); /** * Writes the code needed to serialize a map in the document of a request. * *

Implementations of this method are expected to generate a function body that * returns a value representing the MapShape {@code shape} parameter. * *

For example, given the following Smithy model: * *

{@code
     * map FieldMap {
     *     key: String,
     *     value: Field
     * }
     * }
* *

The function signature for this body will return only {@code error} and have at * least one parameter in scope: *

    *
  • {@code v map[string]*types.Field}: the map to be serialized.
  • *
* *

It will also have any parameters in scope as defined by {@code getAdditionalArguments}. * For json protocols, for instance, smithy has the Value accumulator that can be passed * around to build up the json. * *

The function could end up looking like: * *

{@code
     * func myProtocol_serializeDocumentFieldMap(v map[string]*types.Field, value smithyjson.Value) error {
     *     object := value.Object()
     *     defer object.Close()
     *
     *     for key := range v {
     *         om := object.Key(key)
     *         if vv := v[key]; vv == nil {
     *             om.Null()
     *             continue
     *         }
     *         if err := myProtocol_serializeDocumentField(v[key], om); err != nil {
     *             return err
     *         }
     *     }
     *     return nil
     * }
     * }
* * @param context The generation context. * @param shape The map shape being generated. */ protected abstract void serializeMap(GenerationContext context, MapShape shape); /** * Writes the code needed to serialize a structure in the document of a request. * *

Implementations of this method are expected to generate a function body that * returns a value representing the StructureShape {@code shape} parameter. * *

For example, given the following Smithy model: * *

{@code
     * structure Field {
     *     FooValue: Foo,
     *     BarValue: String,
     * }
     * }
* *

The function signature for this body will return only {@code error} and have at * least one parameter in scope: *

    *
  • {@code v *types.Field}: the structure to be serialized.
  • *
* *

It will also have any parameters in scope as defined by {@code getAdditionalArguments}. * For json protocols, for instance, smithy has the Value accumulator that can be passed * around to build up the json. * *

The function could end up looking like: * *

{@code
     * func myProtocol_serializeDocumentField(v *types.Field, value smithyjson.Value) error {
     *     object := value.Object()
     *     defer object.Close()
     *
     *     if v.FooValue != nil {
     *         ok := object.Key("FooValue")
     *         if err := myProtocol_serializeDocumentFoo(v.FooValue, ok); err != nil {
     *             return err
     *         }
     *     }
     *
     *     if v.BarValue != nil {
     *         ok := object.Key("BarValue")
     *         ok.String(*v.Value)
     *     }
     *
     *     return nil
     * }
     * }
* * @param context The generation context. * @param shape The structure shape being generated. */ protected abstract void serializeStructure(GenerationContext context, StructureShape shape); /** * Writes the code needed to serialize a union in the document of a request. * *

Implementations of this method are expected to generate a function body that * returns a value representing the UnionShape {@code shape} parameter. * *

For example, given the following Smithy model: * *

{@code
     * union Field {
     *     FooValue: Foo,
     *     BarValue: String,
     * }
     * }
* *

The function signature for this body will return only {@code error} and have at * least one parameter in scope: *

    *
  • {@code v types.Field}: the union to be serialized.
  • *
* *

It will also have any parameters in scope as defined by {@code getAdditionalArguments}. * For json protocols, for instance, smithy has the Value accumulator that can be passed * around to build up the json. * *

The function could end up looking like: * *

{@code
     * func myProtocol_serializeDocumentField(v types.Field, value smithyjson.Value) error {
     *     object := value.Object()
     *     defer object.Close()
     *
     *     switch uv := v.(type) {
     *     case *types.FieldFooValue:
     *         ok := object.Key("FooValue")
     *         if err := myProtocol_serializeDocumentFoo(v.FooValue, ok); err != nil {
     *             return err
     *         }
     *     case *types.FieldBarValue:
     *         ok := object.Key("BarValue")
     *         ok.String(*v.Value)
     *     case *types.FieldUnknown:
     *         return fmt.Errorf("unknown member type %T for union %T", uv, v)
     *     }
     *
     *     return nil
     * }
     * }
* * @param context The generation context. * @param shape The union shape being generated. */ protected abstract void serializeUnion(GenerationContext context, UnionShape shape); /** * Generates a function for serializing the input shape, dispatching the body generation * to the supplied function. * * @param shape The shape to generate a serializer for. * @param functionBody An implementation that will generate a function body to serialize the shape. */ private void generateSerFunction( Shape shape, BiConsumer functionBody ) { SymbolProvider symbolProvider = context.getSymbolProvider(); GoWriter writer = context.getWriter().get(); Symbol symbol = symbolProvider.toSymbol(shape); final String functionName; if (serializerNameProvider != null) { functionName = serializerNameProvider.getName(shape, context.getService(), context.getProtocolName()); } else { functionName = ProtocolGenerator.getDocumentSerializerFunctionName( shape, context.getService(), context.getProtocolName()); } String additionalArguments = getAdditionalSerArguments().entrySet().stream() .map(entry -> String.format(", %s %s", entry.getKey(), entry.getValue())) .collect(Collectors.joining()); writer.openBlock("func $L(v $P$L) error {", "}", functionName, symbol, additionalArguments, () -> { functionBody.accept(context, shape); }); writer.write(""); } /** * Gets any additional arguments needed for every serializer function. *

* For example, a json protocol may wish to pass around a {@code smithy/json.Value} builder. * * @return a map of argument name to argument type. */ protected Map getAdditionalSerArguments() { return Collections.emptyMap(); } @Override protected Void getDefault(Shape shape) { return null; } @Override public final Void operationShape(OperationShape shape) { throw new CodegenException("Operation shapes cannot be bound to documents."); } @Override public final Void resourceShape(ResourceShape shape) { throw new CodegenException("Resource shapes cannot be bound to documents."); } @Override public final Void serviceShape(ServiceShape shape) { throw new CodegenException("Service shapes cannot be bound to documents."); } /** * Dispatches to create the body of document shape serialization functions. * * @param shape The document shape to generate serialization for. * @return null */ @Override public final Void documentShape(DocumentShape shape) { generateSerFunction(shape, (c, s) -> serializeDocument(c, s.asDocumentShape().get())); return null; } /** * Dispatches to create the body of list shape serialization functions. * * @param shape The list shape to generate serialization for. * @return null */ @Override public final Void listShape(ListShape shape) { generateSerFunction(shape, (c, s) -> serializeCollection(c, s.asListShape().get())); return null; } /** * Dispatches to create the body of map shape serialization functions. * * @param shape The map shape to generate serialization for. * @return null */ @Override public final Void mapShape(MapShape shape) { generateSerFunction(shape, (c, s) -> serializeMap(c, s.asMapShape().get())); return null; } /** * Dispatches to create the body of set shape serialization functions. * * @param shape The set shape to generate serialization for. * @return null */ @Override public final Void setShape(SetShape shape) { generateSerFunction(shape, (c, s) -> serializeCollection(c, s.asSetShape().get())); return null; } /** * Dispatches to create the body of structure shape serialization functions. * * @param shape The structure shape to generate serialization for. * @return null */ @Override public final Void structureShape(StructureShape shape) { generateSerFunction(shape, (c, s) -> serializeStructure(c, s.asStructureShape().get())); return null; } /** * Dispatches to create the body of union shape serialization functions. * * @param shape The union shape to generate serialization for. * @return null */ @Override public final Void unionShape(UnionShape shape) { generateSerFunction(shape, (c, s) -> serializeUnion(c, s.asUnionShape().get())); return null; } } EndpointHostPrefixMiddleware.java000066400000000000000000000221021463735525100424550ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. * * */ package software.amazon.smithy.go.codegen.integration; import java.util.ArrayList; import java.util.List; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.MiddlewareIdentifier; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.go.codegen.endpoints.EndpointMiddlewareGenerator; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.pattern.SmithyPattern; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.traits.EndpointTrait; /** * EndpointHostPrefixMiddleware adds middlewares to identify * a host prefix and mutate the request URL host if permitted. **/ public class EndpointHostPrefixMiddleware implements GoIntegration { private static final MiddlewareIdentifier MIDDLEWARE_ID = MiddlewareIdentifier.string("EndpointHostPrefix"); final List runtimeClientPlugins = new ArrayList<>(); final List endpointPrefixOperations = new ArrayList<>(); @Override public void processFinalizedModel(GoSettings settings, Model model) { ServiceShape service = settings.getService(model); endpointPrefixOperations.addAll(getOperationsWithEndpointPrefix(model, service)); endpointPrefixOperations.forEach((operation) -> { String middlewareHelperName = getMiddlewareHelperName(operation); runtimeClientPlugins.add(RuntimeClientPlugin.builder() .operationPredicate((m, s, o) -> o.equals(operation)) .registerMiddleware(MiddlewareRegistrar.builder() .resolvedFunction(SymbolUtils.createValueSymbolBuilder(middlewareHelperName).build()) .build()) .build() ); }); } @Override public List getClientPlugins() { return runtimeClientPlugins; } @Override public void writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator delegator ) { endpointPrefixOperations.forEach((operation) -> { delegator.useShapeWriter(operation, (writer) -> { SmithyPattern pattern = operation.expectTrait(EndpointTrait.class).getHostPrefix(); writeMiddleware(writer, model, symbolProvider, operation, pattern); String middlewareName = getMiddlewareName(operation); String middlewareHelperName = getMiddlewareHelperName(operation); writer.addUseImports(SmithyGoDependency.SMITHY_MIDDLEWARE); writer.openBlock("func $L(stack *middleware.Stack) error {", "}", middlewareHelperName, () -> { writer.write( "return stack.Finalize.Insert(&$L{}, $S, middleware.After)", middlewareName, EndpointMiddlewareGenerator.MIDDLEWARE_ID); }); }); }); } private static void writeMiddleware( GoWriter writer, Model model, SymbolProvider symbolProvider, OperationShape operation, SmithyPattern pattern ) { GoStackStepMiddlewareGenerator middlewareGenerator = GoStackStepMiddlewareGenerator.createFinalizeStepMiddleware( getMiddlewareName(operation), MIDDLEWARE_ID ); middlewareGenerator.writeMiddleware(writer, (generator, w) -> { writer.addUseImports(SmithyGoDependency.SMITHY_HTTP_TRANSPORT); writer.addUseImports(SmithyGoDependency.FMT); w.openBlock("if smithyhttp.GetHostnameImmutable(ctx) || " + "smithyhttp.IsEndpointHostPrefixDisabled(ctx) {", "}", () -> { w.write("return next.$L(ctx, in)", generator.getHandleMethodName()); }).write(""); w.write("req, ok := in.Request.(*smithyhttp.Request)"); w.openBlock("if !ok {", "}", () -> { writer.write("return out, metadata, fmt.Errorf(\"unknown transport type %T\", in.Request)"); }).write(""); if (pattern.getLabels().isEmpty()) { w.write("req.URL.Host = $S + req.URL.Host", pattern.toString()); } else { // If the pattern has labels, we need to build up the host prefix using a string builder. writer.addUseImports(SmithyGoDependency.STRINGS); writer.addUseImports(SmithyGoDependency.SMITHY); StructureShape input = ProtocolUtils.expectInput(model, operation); writer.write("opaqueInput := getOperationInput(ctx)"); writer.write("input, ok := opaqueInput.($P)", symbolProvider.toSymbol(input)); w.openBlock("if !ok {", "}", () -> { writer.write("return out, metadata, fmt.Errorf(\"unknown input type %T\", opaqueInput)"); }).write(""); w.write("var prefix strings.Builder"); for (SmithyPattern.Segment segment : pattern.getSegments()) { if (!segment.isLabel()) { w.write("prefix.WriteString($S)", segment.toString()); } else { MemberShape member = input.getMember(segment.getContent()).get(); String memberName = symbolProvider.toMemberName(member); String memberReference = "input." + memberName; // Theoretically this should never be nil or empty by this point unless validation has // been disabled. w.write("if $L == nil {", memberReference).indent(); w.write("return out, metadata, &smithy.SerializationError{Err: " + "fmt.Errorf(\"$L forms part of the endpoint host and so may not be nil\")}", memberName); w.dedent().write("} else if !smithyhttp.ValidHostLabel(*$L) {", memberReference).indent(); w.write("return out, metadata, &smithy.SerializationError{Err: " + "fmt.Errorf(\"$L forms part of the endpoint host and so must match " + "\\\"[a-zA-Z0-9-]{1,63}\\\"" + ", but was \\\"%s\\\"\", *$L)}", memberName, memberReference); w.dedent().openBlock("} else {", "}", () -> { w.write("prefix.WriteString(*$L)", memberReference); }); } } w.write("req.URL.Host = prefix.String() + req.URL.Host"); } w.write(""); w.write("return next.$L(ctx, in)", generator.getHandleMethodName()); }); } /** * Gets a list of the operations decorated with the EndpointTrait. * * @param model Model used for generation. * @param service Service for getting list of operations. * @return list of operations decorated with the EndpointTrait. */ public static List getOperationsWithEndpointPrefix(Model model, ServiceShape service) { List operations = new ArrayList<>(); TopDownIndex.of(model).getContainedOperations(service).stream().forEach((operation) -> { if (!operation.hasTrait(EndpointTrait.ID)) { return; } operations.add(operation); }); return operations; } private static String getMiddlewareName(OperationShape operation) { return String.format("endpointPrefix_op%sMiddleware", operation.getId().getName()); } private static String getMiddlewareHelperName(OperationShape operation) { return String.format("addEndpointPrefix_op%sMiddleware", operation.getId().getName()); } } GoIntegration.java000066400000000000000000000201041463735525100374340ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.Collections; import java.util.List; import java.util.function.Consumer; import software.amazon.smithy.codegen.core.SmithyIntegration; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoSettings.ArtifactType; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.TriConsumer; import software.amazon.smithy.go.codegen.server.ServerProtocolGenerator; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.utils.SmithyUnstableApi; /** * Java SPI for customizing Go code generation, registering * new protocol code generators, renaming shapes, modifying the model, * adding custom code, etc. */ @SmithyUnstableApi public interface GoIntegration extends SmithyIntegration { /** * Gets the sort order of the customization from -128 to 127. * *

Customizations are applied according to this sort order. Lower values * are executed before higher values (for example, -128 comes before 0, * comes before 127). Customizations default to 0, which is the middle point * between the minimum and maximum order values. The customization * applied later can override the runtime configurations that provided * by customizations applied earlier. * * @return Returns the sort order, defaulting to 0. */ default byte getOrder() { return 0; } default ArtifactType getArtifactType() { return ArtifactType.CLIENT; } /** * Preprocess the model before code generation. * *

This can be used to remove unsupported features, remove traits * from shapes (e.g., make members optional), etc. * * @param model model definition. * @param settings Setting used to generate. * @return Returns the updated model. */ default Model preprocessModel(Model model, GoSettings settings) { return model; } /** * Updates the {@link SymbolProvider} used when generating code. * *

This can be used to customize the names of shapes, the package * that code is generated into, add dependencies, add imports, etc. * * @param settings Setting used to generate. * @param model Model being generated. * @param symbolProvider The original {@code SymbolProvider}. * @return The decorated {@code SymbolProvider}. */ default SymbolProvider decorateSymbolProvider( GoSettings settings, Model model, SymbolProvider symbolProvider ) { return symbolProvider; } /** * Writes additional files. * * Called each time a writer is used that defines a shape. * *

Any mutations made on the writer (for example, adding * section interceptors) are removed after the callback has completed; * the callback is invoked in between pushing and popping state from * the writer. * * @param settings Settings used to generate. * @param model Model to generate from. * @param symbolProvider Symbol provider used for codegen. * @param writer Writer that will be used. * @param definedShape Shape that is being defined in the writer. */ default void onShapeWriterUse( GoSettings settings, Model model, SymbolProvider symbolProvider, GoWriter writer, Shape definedShape ) { // pass } /** * Writes additional files. * * @param settings Settings used to generate. * @param model Model to generate from. * @param symbolProvider Symbol provider used for codegen. * @param writerFactory A factory function that takes the name of a file * to write and a {@code Consumer} that receives a * {@link GoSettings} to perform the actual writing to the file. * @deprecated use {@link #writeAdditionalFiles(GoCodegenContext)}. */ default void writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, TriConsumer> writerFactory ) { // pass } /** * Writes additional files. * * @param settings Settings used to generate. * @param model Model to generate from. * @param symbolProvider Symbol provider used for codegen. * @param goDelegator GoDelegator used to manage writer for the file. * @deprecated use {@link #writeAdditionalFiles(GoCodegenContext)}. */ default void writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator goDelegator ) { // pass } /** * Writes additional files. * @param ctx The codegen context. */ default void writeAdditionalFiles(GoCodegenContext ctx) { // pass } /** * Gets a list of protocol generators to register. * * @return Returns the list of protocol generators to register. */ default List getProtocolGenerators() { return Collections.emptyList(); } /** * Gets a list of server protocol generators to register. Protocol generators should generally be written to accept * the codegen context at construction time, such that all the model information necessary for codegen is available. */ default List getServerProtocolGenerators(GoCodegenContext ctx) { return Collections.emptyList(); } /** * Processes the finalized model before runtime plugins are consumed and * code generation starts. This plugin can be used to add RuntimeClientPlugins * to the integration's list of plugin. * * @param settings Settings used to generate. * @param model Model to generate from. */ default void processFinalizedModel(GoSettings settings, Model model) { // pass } /** * Gets a list of plugins to apply to the generated client. * * @return Returns the list of RuntimePlugins to apply to the client. */ default List getClientPlugins() { return Collections.emptyList(); } /** * Gets a list of plugins to apply to the generated client. * * @return Returns the list of RuntimePlugins to apply to the client. */ default List getClientPlugins(Model model, ServiceShape service) { return getClientPlugins().stream().filter(plugin -> plugin.matchesService(model, service)).toList(); } /** * Processes the given serviceId and may return a unmodified, modified, or replacement value. * * @param settings Settings used to generate * @param model model to generate from * @param serviceId the serviceId * @return the new serviceId */ default String processServiceId(GoSettings settings, Model model, String serviceId) { return serviceId; } default void renderPreEndpointResolutionHook(GoSettings settings, GoWriter writer, Model model) { // pass } default void renderPostEndpointResolutionHook(GoSettings settings, GoWriter writer, Model model) { // pass } } HttpBindingProtocolGenerator.java000066400000000000000000002233021463735525100424730ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import static software.amazon.smithy.go.codegen.integration.ProtocolUtils.requiresDocumentSerdeFunction; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.function.BiConsumer; import java.util.logging.Logger; import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.ApplicationProtocol; import software.amazon.smithy.go.codegen.CodegenUtils; import software.amazon.smithy.go.codegen.GoEventStreamIndex; import software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator; import software.amazon.smithy.go.codegen.GoValueAccessUtils; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; import software.amazon.smithy.go.codegen.trait.NoSerializeTrait; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.EventStreamIndex; import software.amazon.smithy.model.knowledge.EventStreamInfo; import software.amazon.smithy.model.knowledge.HttpBinding; import software.amazon.smithy.model.knowledge.HttpBindingIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.CollectionShape; 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.ServiceShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.ToShapeId; import software.amazon.smithy.model.shapes.UnionShape; import software.amazon.smithy.model.traits.EnumTrait; import software.amazon.smithy.model.traits.HttpTrait; import software.amazon.smithy.model.traits.MediaTypeTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.model.traits.TimestampFormatTrait; import software.amazon.smithy.model.traits.TimestampFormatTrait.Format; import software.amazon.smithy.utils.OptionalUtils; /** * Abstract implementation useful for all protocols that use HTTP bindings. */ public abstract class HttpBindingProtocolGenerator implements ProtocolGenerator { private static final Logger LOGGER = Logger.getLogger(HttpBindingProtocolGenerator.class.getName()); private final boolean isErrorCodeInBody; private final Set serializeDocumentBindingShapes = new TreeSet<>(); private final Set deserializeDocumentBindingShapes = new TreeSet<>(); private final Set deserializingErrorShapes = new TreeSet<>(); private final Map deserializerOverrides = new HashMap<>(); /** * Creates a Http binding protocol generator. * * @param isErrorCodeInBody A boolean that indicates if the error code for the implementing protocol is located in * the error response body, meaning this generator will parse the body before attempting to * load an error code. */ public HttpBindingProtocolGenerator(boolean isErrorCodeInBody) { this.isErrorCodeInBody = isErrorCodeInBody; } @Override public ApplicationProtocol getApplicationProtocol() { return ApplicationProtocol.createDefaultHttpApplicationProtocol(); } @Override public void generateSharedSerializerComponents(GenerationContext context) { serializeDocumentBindingShapes.addAll(ProtocolUtils.resolveRequiredDocumentShapeSerde( context.getModel(), serializeDocumentBindingShapes)); generateDocumentBodyShapeSerializers(context, serializeDocumentBindingShapes); } /** * Get the operations with HTTP Bindings. * * @param context the generation context * @return the list of operation shapes */ public Set getHttpBindingOperations(GenerationContext context) { TopDownIndex topDownIndex = context.getModel().getKnowledge(TopDownIndex.class); Set containedOperations = new TreeSet<>(); for (OperationShape operation : topDownIndex.getContainedOperations(context.getService())) { OptionalUtils.ifPresentOrElse( operation.getTrait(HttpTrait.class), httpTrait -> containedOperations.add(operation), () -> LOGGER.warning(String.format( "Unable to fetch %s protocol request bindings for %s because it does not have an " + "http binding trait", getProtocol(), operation.getId())) ); } return containedOperations; } @Override public void generateRequestSerializers(GenerationContext context) { Set operations = getHttpBindingOperations(context); for (OperationShape operation : operations) { generateOperationSerializer(context, operation); } GoEventStreamIndex goEventStreamIndex = GoEventStreamIndex.of(context.getModel()); goEventStreamIndex.getInputEventStreams(context.getService()).ifPresent(shapeIdSetMap -> shapeIdSetMap.forEach((shapeId, eventStreamInfos) -> { generateEventStreamSerializers(context, context.getModel().expectShape(shapeId, UnionShape.class), eventStreamInfos); })); } /** * Generate the event stream serializers for the given event stream target and associated operations. * * @param context the generation context * @param eventUnion the event stream union * @param eventStreamInfos the event stream infos */ protected abstract void generateEventStreamSerializers( GenerationContext context, UnionShape eventUnion, Set eventStreamInfos ); /** * Generate the event stream deserializers for the given event stream target and asscioated operations. * * @param context the generation context * @param eventUnion the event stream union * @param eventStreamInfos the event stream infos */ protected abstract void generateEventStreamDeserializers( GenerationContext context, UnionShape eventUnion, Set eventStreamInfos ); /** * Gets the default serde format for timestamps. * * @return Returns the default format. */ protected abstract Format getDocumentTimestampFormat(); /** * Gets the default content-type when a document is synthesized in the body. * * @return Returns the default content-type. */ protected abstract String getDocumentContentType(); private void generateOperationSerializer(GenerationContext context, OperationShape operation) { generateOperationSerializerMiddleware(context, operation); generateOperationHttpBindingSerializer(context, operation); Optional streamInfo = EventStreamIndex.of(context.getModel()).getInputInfo(operation); if (!CodegenUtils.isStubSynthetic(ProtocolUtils.expectInput(context.getModel(), operation)) && streamInfo.isEmpty()) { generateOperationDocumentSerializer(context, operation); addOperationDocumentShapeBindersForSerializer(context, operation); } } /** * Generates the operation document serializer function. * * @param context the generation context * @param operation the operation shape being generated */ protected abstract void generateOperationDocumentSerializer(GenerationContext context, OperationShape operation); /** * Adds the top-level shapes from the operation that bind to the body document that require serializer functions. * * @param context the generator context * @param operation the operation to add document binders from */ private void addOperationDocumentShapeBindersForSerializer(GenerationContext context, OperationShape operation) { Model model = context.getModel(); // Walk and add members shapes to the list that will require serializer functions Collection bindings = HttpBindingIndex.of(model) .getRequestBindings(operation).values(); for (HttpBinding binding : bindings) { MemberShape memberShape = binding.getMember(); Shape targetShape = model.expectShape(memberShape.getTarget()); // Check if the input shape has a members that target the document or payload and require serializers. // If an operation has an input event stream it will have seperate serializers generated. if (requiresDocumentSerdeFunction(targetShape) && (binding.getLocation() == HttpBinding.Location.DOCUMENT || binding.getLocation() == HttpBinding.Location.PAYLOAD)) { serializeDocumentBindingShapes.add(targetShape); } } } private void generateOperationSerializerMiddleware(GenerationContext context, OperationShape operation) { SymbolProvider symbolProvider = context.getSymbolProvider(); Model model = context.getModel(); ServiceShape service = context.getService(); Shape inputShape = model.expectShape(operation.getInput() .orElseThrow(() -> new CodegenException("expect input shape for operation: " + operation.getId()))); Symbol inputSymbol = symbolProvider.toSymbol(inputShape); ApplicationProtocol applicationProtocol = getApplicationProtocol(); Symbol requestType = applicationProtocol.getRequestType(); HttpTrait httpTrait = operation.expectTrait(HttpTrait.class); GoStackStepMiddlewareGenerator middleware = GoStackStepMiddlewareGenerator.createSerializeStepMiddleware( ProtocolGenerator.getSerializeMiddlewareName(operation.getId(), service, getProtocolName()), ProtocolUtils.OPERATION_SERIALIZER_MIDDLEWARE_ID); middleware.writeMiddleware(context.getWriter().get(), (generator, writer) -> { writer.addUseImports(SmithyGoDependency.FMT); writer.addUseImports(SmithyGoDependency.SMITHY); writer.addUseImports(SmithyGoDependency.SMITHY_HTTP_BINDING); // cast input request to smithy transport type, check for failures writer.write("request, ok := in.Request.($P)", requestType); writer.openBlock("if !ok {", "}", () -> { writer.write("return out, metadata, " + "&smithy.SerializationError{Err: fmt.Errorf(\"unknown transport type %T\", in.Request)}" ); }); writer.write(""); // cast input parameters type to the input type of the operation writer.write("input, ok := in.Parameters.($P)", inputSymbol); writer.write("_ = input"); writer.openBlock("if !ok {", "}", () -> { writer.write("return out, metadata, " + "&smithy.SerializationError{Err: fmt.Errorf(\"unknown input parameters type %T\"," + " in.Parameters)}"); }); writer.write(""); writer.write("opPath, opQuery := httpbinding.SplitURI($S)", httpTrait.getUri()); writer.write("request.URL.Path = smithyhttp.JoinPath(request.URL.Path, opPath)"); writer.write("request.URL.RawQuery = smithyhttp.JoinRawQuery(request.URL.RawQuery, opQuery)"); writer.write("request.Method = $S", httpTrait.getMethod()); writer.write( """ var restEncoder $P if request.URL.RawPath == "" { restEncoder, err = $T(request.URL.Path, request.URL.RawQuery, request.Header) } else { request.URL.RawPath = $T(request.URL.RawPath, opPath) restEncoder, err = $T(request.URL.Path, request.URL.RawPath, request.URL.RawQuery, request.Header) } """, SymbolUtils.createPointableSymbolBuilder( "Encoder", SmithyGoDependency.SMITHY_HTTP_BINDING).build(), SymbolUtils.createValueSymbolBuilder( "NewEncoder", SmithyGoDependency.SMITHY_HTTP_BINDING).build(), SymbolUtils.createValueSymbolBuilder( "JoinPath", SmithyGoDependency.SMITHY_HTTP_TRANSPORT).build(), SymbolUtils.createValueSymbolBuilder( "NewEncoderWithRawPath", SmithyGoDependency.SMITHY_HTTP_BINDING).build() ); writer.openBlock("if err != nil {", "}", () -> { writer.write("return out, metadata, &smithy.SerializationError{Err: err}"); }); writer.write(""); // we only generate an operations http bindings function if there are bindings if (isOperationWithRestRequestBindings(model, operation)) { String serFunctionName = ProtocolGenerator.getOperationHttpBindingsSerFunctionName( inputShape, service, getProtocolName()); writer.openBlock("if err := $L(input, restEncoder); err != nil {", "}", serFunctionName, () -> { writer.write("return out, metadata, &smithy.SerializationError{Err: err}"); }); writer.write(""); } // Don't consider serializing the body if the input shape is a stubbed synthetic clone, without an // archetype. if (!CodegenUtils.isStubSynthetic(ProtocolUtils.expectInput(model, operation))) { Optional eventStreamInfo = EventStreamIndex.of(model).getInputInfo(operation); // document bindings vs payload bindings vs event streams HttpBindingIndex httpBindingIndex = HttpBindingIndex.of(model); boolean hasDocumentBindings = httpBindingIndex .getRequestBindings(operation, HttpBinding.Location.DOCUMENT) .stream().anyMatch(httpBinding -> eventStreamInfo.map(streamInfo -> !streamInfo.getEventStreamMember().equals(httpBinding.getMember())).orElse(true)); Optional payloadBinding = httpBindingIndex.getRequestBindings(operation, HttpBinding.Location.PAYLOAD).stream() .filter(httpBinding -> eventStreamInfo.map(streamInfo -> !streamInfo.getEventStreamMember().equals(httpBinding.getMember())).orElse(true)) .findFirst(); if (eventStreamInfo.isPresent() && (hasDocumentBindings || payloadBinding.isPresent())) { throw new CodegenException("HTTP Binding Protocol unexpected document or payload bindings with " + "input event stream"); } if (eventStreamInfo.isPresent()) { writeOperationSerializerMiddlewareEventStreamSetup(context, eventStreamInfo.get()); } else if (hasDocumentBindings) { // delegate the setup and usage of the document serializer function for the protocol writeMiddlewareDocumentSerializerDelegator(context, operation, generator); } else if (payloadBinding.isPresent()) { // delegate the setup and usage of the payload serializer function for the protocol MemberShape memberShape = payloadBinding.get().getMember(); writeMiddlewarePayloadSerializerDelegator(context, memberShape); } writer.write(""); } // Serialize HTTP request with payload, if set. writer.openBlock("if request.Request, err = restEncoder.Encode(request.Request); err != nil {", "}", () -> { writer.write("return out, metadata, &smithy.SerializationError{Err: err}"); }); writer.write("in.Request = request"); writer.write(""); writer.write("return next.$L(ctx, in)", generator.getHandleMethodName()); }); } protected abstract void writeOperationSerializerMiddlewareEventStreamSetup( GenerationContext context, EventStreamInfo eventStreamInfo ); // Generates operation deserializer middleware that delegates to appropriate deserializers for the error, // output shapes for the operation. private void generateOperationDeserializerMiddleware(GenerationContext context, OperationShape operation) { SymbolProvider symbolProvider = context.getSymbolProvider(); Model model = context.getModel(); ServiceShape service = context.getService(); ApplicationProtocol applicationProtocol = getApplicationProtocol(); Symbol responseType = applicationProtocol.getResponseType(); GoWriter goWriter = context.getWriter().get(); GoStackStepMiddlewareGenerator middleware = GoStackStepMiddlewareGenerator.createDeserializeStepMiddleware( ProtocolGenerator.getDeserializeMiddlewareName(operation.getId(), service, getProtocolName()), ProtocolUtils.OPERATION_DESERIALIZER_MIDDLEWARE_ID); String errorFunctionName = ProtocolGenerator.getOperationErrorDeserFunctionName( operation, service, context.getProtocolName()); middleware.writeMiddleware(goWriter, (generator, writer) -> { writer.addUseImports(SmithyGoDependency.FMT); writer.write("out, metadata, err = next.$L(ctx, in)", generator.getHandleMethodName()); writer.write("if err != nil { return out, metadata, err }"); writer.write(""); writer.write("response, ok := out.RawResponse.($P)", responseType); writer.openBlock("if !ok {", "}", () -> { writer.addUseImports(SmithyGoDependency.SMITHY); writer.write(String.format("return out, metadata, &smithy.DeserializationError{Err: %s}", "fmt.Errorf(\"unknown transport type %T\", out.RawResponse)")); }); writer.write(""); writer.openBlock("if response.StatusCode < 200 || response.StatusCode >= 300 {", "}", () -> { writer.write("return out, metadata, $L(response, &metadata)", errorFunctionName); }); Shape outputShape = model.expectShape(operation.getOutput() .orElseThrow(() -> new CodegenException("expect output shape for operation: " + operation.getId())) ); Symbol outputSymbol = symbolProvider.toSymbol(outputShape); // initialize out.Result as output structure shape writer.write("output := &$T{}", outputSymbol); writer.write("out.Result = output"); writer.write(""); // Output shape HTTP binding middleware generation if (isShapeWithRestResponseBindings(model, operation)) { String deserFuncName = ProtocolGenerator.getOperationHttpBindingsDeserFunctionName( outputShape, service, getProtocolName()); writer.write("err= $L(output, response)", deserFuncName); writer.openBlock("if err != nil {", "}", () -> { writer.addUseImports(SmithyGoDependency.SMITHY); writer.write(String.format("return out, metadata, &smithy.DeserializationError{Err: %s}", "fmt.Errorf(\"failed to decode response with invalid Http bindings, %w\", err)")); }); writer.write(""); } Optional streamInfoOptional = EventStreamIndex.of(model).getOutputInfo(operation); // Discard without deserializing the response if the input shape is a stubbed synthetic clone // without an archetype. if (CodegenUtils.isStubSynthetic(ProtocolUtils.expectOutput(model, operation)) && streamInfoOptional.isEmpty()) { writer.addUseImports(SmithyGoDependency.IOUTIL); writer.openBlock("if _, err = io.Copy(ioutil.Discard, response.Body); err != nil {", "}", () -> { writer.openBlock("return out, metadata, &smithy.DeserializationError{", "}", () -> { writer.write("Err: fmt.Errorf(\"failed to discard response body, %w\", err),"); }); }); } else { boolean hasBodyBinding = HttpBindingIndex.of(model).getResponseBindings(operation).values().stream() .filter(httpBinding -> httpBinding.getLocation() == HttpBinding.Location.DOCUMENT || httpBinding.getLocation() == HttpBinding.Location.PAYLOAD) .anyMatch(httpBinding -> streamInfoOptional.map(esi -> !esi.getEventStreamMember() .equals(httpBinding.getMember())).orElse(true)); if (hasBodyBinding && streamInfoOptional.isPresent()) { throw new CodegenException("HTTP Binding Protocol unexpected document or payload bindings with " + "output event stream"); } if (hasBodyBinding) { // Output Shape Document Binding middleware generation writeMiddlewareDocumentDeserializerDelegator(context, operation, generator); } } writer.write(""); writer.write("return out, metadata, err"); }); goWriter.write(""); Set errorShapes = HttpProtocolGeneratorUtils.generateErrorDispatcher( context, operation, responseType, this::writeErrorMessageCodeDeserializer, this::getOperationErrors); deserializingErrorShapes.addAll(errorShapes); deserializeDocumentBindingShapes.addAll(errorShapes); } /** * Writes a code snippet that gets the error code and error message. * *

Four parameters will be available in scope: *

    *
  • {@code response: smithyhttp.HTTPResponse}: the HTTP response received.
  • *
  • {@code errorBody: bytes.BytesReader}: the HTTP response body.
  • *
  • {@code errorMessage: string}: the error message initialized to a default value.
  • *
  • {@code errorCode: string}: the error code initialized to a default value.
  • *
* * @param context the generation context. */ protected abstract void writeErrorMessageCodeDeserializer(GenerationContext context); /** * Generate the document serializer logic for the serializer middleware body. * * @param context the generation context * @param operation the operation * @param generator middleware generator definition */ protected abstract void writeMiddlewareDocumentSerializerDelegator( GenerationContext context, OperationShape operation, GoStackStepMiddlewareGenerator generator ); /** * Writes a payload content-type header setter. * * @param writer the {@link GoWriter}. * @param payloadShape the payload shape. */ protected void writeSetPayloadShapeHeader(GoWriter writer, Shape payloadShape) { writer.pushState(); writer.putContext("withIsDefaultContentType", SymbolUtils.createValueSymbolBuilder( "SetIsContentTypeDefaultValue", SmithyGoDependency.SMITHY_HTTP_TRANSPORT).build()); writer.putContext("payloadMediaType", getPayloadShapeMediaType(payloadShape)); writer.write(""" if !restEncoder.HasHeader("Content-Type") { ctx = $withIsDefaultContentType:T(ctx, true) restEncoder.SetHeader("Content-Type").String($payloadMediaType:S) } """); writer.popState(); } /** * Writes the stream setter that set the operand as the HTTP body. * * @param writer the {@link GoWriter}. * @param operand the operand for the value to be set as the HTTP body. */ protected void writeSetStream(GoWriter writer, String operand) { writer.write(""" if request, err = request.SetStream($L); err != nil { return out, metadata, &smithy.SerializationError{Err: err} }""", operand); } /** * Generate the payload serializer logic for the serializer middleware body. * * @param context the generation context * @param memberShape the payload target member */ protected void writeMiddlewarePayloadSerializerDelegator( GenerationContext context, MemberShape memberShape ) { GoWriter writer = context.getWriter().get(); Model model = context.getModel(); Shape payloadShape = model.expectShape(memberShape.getTarget()); if (payloadShape.hasTrait(StreamingTrait.class)) { writeSetPayloadShapeHeader(writer, payloadShape); GoValueAccessUtils.writeIfNonZeroValueMember(context.getModel(), context.getSymbolProvider(), writer, memberShape, "input", (s) -> { writer.write("payload := $L", s); writeSetStream(writer, "payload"); }); } else if (payloadShape.isBlobShape()) { writeSetPayloadShapeHeader(writer, payloadShape); GoValueAccessUtils.writeIfNonZeroValueMember(context.getModel(), context.getSymbolProvider(), writer, memberShape, "input", (s) -> { writer.addUseImports(SmithyGoDependency.BYTES); writer.write("payload := bytes.NewReader($L)", s); writeSetStream(writer, "payload"); }); } else if (payloadShape.isStringShape()) { writeSetPayloadShapeHeader(writer, payloadShape); GoValueAccessUtils.writeIfNonZeroValueMember(context.getModel(), context.getSymbolProvider(), writer, memberShape, "input", (s) -> { writer.addUseImports(SmithyGoDependency.STRINGS); if (payloadShape.hasTrait(EnumTrait.class)) { writer.write("payload := strings.NewReader(string($L))", s); } else { writer.write("payload := strings.NewReader(*$L)", s); } writeSetStream(writer, "payload"); }); } else { writeMiddlewarePayloadAsDocumentSerializerDelegator(context, memberShape, "input"); } } /** * Returns the MediaType for the payload shape derived from the MediaTypeTrait, shape type, or * document content type. * * @param payloadShape shape bound to the payload. * @return string for media type. */ private String getPayloadShapeMediaType(Shape payloadShape) { Optional mediaTypeTrait = payloadShape.getTrait(MediaTypeTrait.class); if (mediaTypeTrait.isPresent()) { return mediaTypeTrait.get().getValue(); } if (payloadShape.isBlobShape()) { return "application/octet-stream"; } if (payloadShape.isStringShape()) { return "text/plain"; } return getDocumentContentType(); } /** * Generate the payload serializers with document serializer logic for the serializer middleware body. * * @param context the generation context * @param memberShape the payload target member * @param operand the operand that is used to access the member value */ protected abstract void writeMiddlewarePayloadAsDocumentSerializerDelegator( GenerationContext context, MemberShape memberShape, String operand ); /** * Generate the document deserializer logic for the deserializer middleware body. * * @param context the generation context * @param operation the operation * @param generator middleware generator definition */ protected abstract void writeMiddlewareDocumentDeserializerDelegator( GenerationContext context, OperationShape operation, GoStackStepMiddlewareGenerator generator ); private boolean isRestBinding(HttpBinding.Location location) { return location == HttpBinding.Location.HEADER || location == HttpBinding.Location.PREFIX_HEADERS || location == HttpBinding.Location.LABEL || location == HttpBinding.Location.QUERY || location == HttpBinding.Location.QUERY_PARAMS || location == HttpBinding.Location.RESPONSE_CODE; } // returns whether an operation shape has Rest Request Bindings private boolean isOperationWithRestRequestBindings(Model model, OperationShape operationShape) { Collection bindings = HttpBindingIndex.of(model) .getRequestBindings(operationShape).values(); for (HttpBinding binding : bindings) { if (isRestBinding(binding.getLocation())) { return true; } } return false; } /** * Returns whether a shape has rest response bindings. * The shape can be an operation shape, error shape or an output shape. * * @param model the model * @param shape the shape with possible presence of rest response bindings * @return boolean indicating presence of rest response bindings in the shape */ protected boolean isShapeWithRestResponseBindings(Model model, Shape shape) { Collection bindings = HttpBindingIndex.of(model).getResponseBindings(shape).values(); for (HttpBinding binding : bindings) { if (isRestBinding(binding.getLocation())) { return true; } } return false; } private void generateOperationHttpBindingSerializer(GenerationContext context, OperationShape operation) { SymbolProvider symbolProvider = context.getSymbolProvider(); Model model = context.getModel(); GoWriter writer = context.getWriter().get(); Shape inputShape = model.expectShape(operation.getInput() .orElseThrow(() -> new CodegenException("missing input shape for operation: " + operation.getId()))); HttpBindingIndex bindingIndex = model.getKnowledge(HttpBindingIndex.class); List bindings = bindingIndex.getRequestBindings(operation).values().stream() .filter(httpBinding -> isRestBinding(httpBinding.getLocation())) .sorted(Comparator.comparing(HttpBinding::getMember)) .collect(Collectors.toList()); Symbol httpBindingEncoder = getHttpBindingEncoderSymbol(); Symbol inputSymbol = symbolProvider.toSymbol(inputShape); String functionName = ProtocolGenerator.getOperationHttpBindingsSerFunctionName( inputShape, context.getService(), getProtocolName()); writer.addUseImports(SmithyGoDependency.FMT); writer.openBlock("func $L(v $P, encoder $P) error {", "}", functionName, inputSymbol, httpBindingEncoder, () -> { writer.openBlock("if v == nil {", "}", () -> { writer.write("return fmt.Errorf(\"unsupported serialization of nil %T\", v)"); }); writer.write(""); for (HttpBinding binding : bindings.stream() .filter(NoSerializeTrait.excludeNoSerializeHttpBindingMembers()) .collect(Collectors.toList())) { writeHttpBindingMember(context, binding); writer.write(""); } writer.write("return nil"); }); writer.write(""); } private Symbol getHttpBindingEncoderSymbol() { return SymbolUtils.createPointableSymbolBuilder("Encoder", SmithyGoDependency.SMITHY_HTTP_BINDING).build(); } private void generateHttpBindingTimestampSerializer( Model model, GoWriter writer, MemberShape memberShape, HttpBinding.Location location, String operand, BiConsumer locationEncoder ) { writer.addUseImports(SmithyGoDependency.SMITHY_TIME); TimestampFormatTrait.Format format = model.getKnowledge(HttpBindingIndex.class).determineTimestampFormat( memberShape, location, getDocumentTimestampFormat()); switch (format) { case DATE_TIME: locationEncoder.accept(writer, "String(smithytime.FormatDateTime(" + operand + "))"); break; case HTTP_DATE: locationEncoder.accept(writer, "String(smithytime.FormatHTTPDate(" + operand + "))"); break; case EPOCH_SECONDS: locationEncoder.accept(writer, "Double(smithytime.FormatEpochSeconds(" + operand + "))"); break; default: throw new CodegenException("Unknown timestamp format"); } } private boolean isHttpDateTimestamp(Model model, HttpBinding.Location location, MemberShape memberShape) { Shape targetShape = model.expectShape(memberShape.getTarget().toShapeId()); if (targetShape.getType() != ShapeType.TIMESTAMP) { return false; } TimestampFormatTrait.Format format = HttpBindingIndex.of(model).determineTimestampFormat( memberShape, location, getDocumentTimestampFormat()); return format == Format.HTTP_DATE; } private void writeHttpBindingSetter( GenerationContext context, GoWriter writer, MemberShape memberShape, HttpBinding.Location location, String operand, BiConsumer locationEncoder ) { Model model = context.getModel(); Shape targetShape = model.expectShape(memberShape.getTarget()); // We only need to dereference if we pass the shape around as reference in Go. // Note we make two exceptions here: big.Int and big.Float should still be passed as reference to the helper // method as they can be arbitrarily large. operand = CodegenUtils.getAsValueIfDereferencable(GoPointableIndex.of(context.getModel()), memberShape, operand); switch (targetShape.getType()) { case BOOLEAN: locationEncoder.accept(writer, "Boolean(" + operand + ")"); break; case STRING: operand = targetShape.hasTrait(EnumTrait.class) ? "string(" + operand + ")" : operand; locationEncoder.accept(writer, "String(" + operand + ")"); break; case ENUM: operand = "string(" + operand + ")"; locationEncoder.accept(writer, "String(" + operand + ")"); break; case TIMESTAMP: generateHttpBindingTimestampSerializer(model, writer, memberShape, location, operand, locationEncoder); break; case BYTE: locationEncoder.accept(writer, "Byte(" + operand + ")"); break; case SHORT: locationEncoder.accept(writer, "Short(" + operand + ")"); break; case INTEGER: case INT_ENUM: locationEncoder.accept(writer, "Integer(" + operand + ")"); break; case LONG: locationEncoder.accept(writer, "Long(" + operand + ")"); break; case FLOAT: locationEncoder.accept(writer, "Float(" + operand + ")"); break; case DOUBLE: locationEncoder.accept(writer, "Double(" + operand + ")"); break; case BIG_INTEGER: locationEncoder.accept(writer, "BigInteger(" + operand + ")"); break; case BIG_DECIMAL: locationEncoder.accept(writer, "BigDecimal(" + operand + ")"); break; default: throw new CodegenException("unexpected shape type " + targetShape.getType()); } } private void writeHttpBindingMember( GenerationContext context, HttpBinding binding ) { GoWriter writer = context.getWriter().get(); Model model = context.getModel(); MemberShape memberShape = binding.getMember(); Shape targetShape = model.expectShape(memberShape.getTarget()); HttpBinding.Location location = binding.getLocation(); // return an error if member shape targets location label, but is unset. if (location.equals(HttpBinding.Location.LABEL)) { // labels must always be set to be serialized on URI, and non empty strings, GoValueAccessUtils.writeIfZeroValueMember(context.getModel(), context.getSymbolProvider(), writer, memberShape, "v", false, true, operand -> { writer.addUseImports(SmithyGoDependency.SMITHY); writer.write("return &smithy.SerializationError { " + "Err: fmt.Errorf(\"input member $L must not be empty\")}", memberShape.getMemberName()); }); } boolean allowZeroStrings = location != HttpBinding.Location.HEADER; GoValueAccessUtils.writeIfNonZeroValueMember(context.getModel(), context.getSymbolProvider(), writer, memberShape, "v", allowZeroStrings, memberShape.isRequired(), (operand) -> { final String locationName = binding.getLocationName().isEmpty() ? memberShape.getMemberName() : binding.getLocationName(); switch (location) { case HEADER: writer.write("locationName := $S", getCanonicalHeader(locationName)); writeHeaderBinding(context, memberShape, operand, location, "locationName", "encoder"); break; case PREFIX_HEADERS: MemberShape valueMemberShape = model.expectShape(targetShape.getId(), MapShape.class).getValue(); Shape valueMemberTarget = model.expectShape(valueMemberShape.getTarget()); if (targetShape.getType() != ShapeType.MAP) { throw new CodegenException("Unexpected prefix headers target shape " + valueMemberTarget.getType() + ", " + valueMemberShape.getId()); } writer.write("hv := encoder.Headers($S)", getCanonicalHeader(locationName)); writer.addUseImports(SmithyGoDependency.NET_HTTP); writer.openBlock("for mapKey, mapVal := range $L {", "}", operand, () -> { GoValueAccessUtils.writeIfNonZeroValue(context.getModel(), writer, valueMemberShape, "mapVal", false, false, () -> { writeHeaderBinding(context, valueMemberShape, "mapVal", location, "http.CanonicalHeaderKey(mapKey)", "hv"); }); }); break; case LABEL: writeHttpBindingSetter(context, writer, memberShape, location, operand, (w, s) -> { w.openBlock("if err := encoder.SetURI($S).$L; err != nil {", "}", locationName, s, () -> { w.write("return err"); }); }); break; case QUERY: writeQueryBinding(context, memberShape, targetShape, operand, location, locationName, "encoder", false); break; case QUERY_PARAMS: MemberShape queryMapValueMemberShape = CodegenUtils.expectMapShape(targetShape).getValue(); Shape queryMapValueTargetShape = model.expectShape(queryMapValueMemberShape.getTarget()); MemberShape queryMapKeyMemberShape = CodegenUtils.expectMapShape(targetShape).getKey(); writer.openBlock("for qkey, qvalue := range $L {", "}", operand, () -> { writer.write("if encoder.HasQuery(qkey) { continue }"); writeQueryBinding(context, queryMapKeyMemberShape, queryMapValueTargetShape, "qvalue", location, "qkey", "encoder", true); }); break; default: throw new CodegenException("unexpected http binding found"); } }); } /** * Writes query bindings, as per the target shape. This method is shared * between members modeled with Location.Query and Location.QueryParams. * Precedence across Location.Query and Location.QueryParams is handled * outside the scope of this function. * * @param context is the generation context * @param memberShape is the member shape for which query is serialized * @param targetShape is the target shape of the query member. * This can either be string, or a list/set of string. * @param operand is the member value accessor . * @param location is the location of the member - can be Location.Query * or Location.QueryParams. * @param locationName is the key for which query is encoded. * @param dest is the query encoder destination. * @param isQueryParams boolean representing if Location used for query binding is * QUERY_PARAMS. */ private void writeQueryBinding( GenerationContext context, MemberShape memberShape, Shape targetShape, String operand, HttpBinding.Location location, String locationName, String dest, boolean isQueryParams ) { GoWriter writer = context.getWriter().get(); if (targetShape instanceof CollectionShape) { MemberShape collectionMember = CodegenUtils.expectCollectionShape(targetShape) .getMember(); writer.openBlock("for i := range $L {", "}", operand, () -> { GoValueAccessUtils.writeIfZeroValue(context.getModel(), writer, collectionMember, operand + "[i]", () -> writer.write("continue")); String addQuery = String.format("$L.AddQuery(%s).$L", isQueryParams ? "$L" : "$S"); writeHttpBindingSetter(context, writer, collectionMember, location, operand + "[i]", (w, s) -> w.writeInline(addQuery, dest, locationName, s)); }); return; } String setQuery = String.format("$L.SetQuery(%s).$L", isQueryParams ? "$L" : "$S"); writeHttpBindingSetter(context, writer, memberShape, location, operand, (w, s) -> w.writeInline(setQuery, dest, locationName, s)); } private void writeHeaderBinding( GenerationContext context, MemberShape memberShape, String operand, HttpBinding.Location location, String locationName, String dest ) { GoWriter writer = context.getWriter().get(); Model model = context.getModel(); Shape targetShape = model.expectShape(memberShape.getTarget()); if (!(targetShape instanceof CollectionShape)) { String op = conditionallyBase64Encode(context, writer, targetShape, operand); writeHttpBindingSetter(context, writer, memberShape, location, op, (w, s) -> { w.writeInline("$L.SetHeader($L).$L", dest, locationName, s); }); return; } MemberShape collectionMemberShape = CodegenUtils.expectCollectionShape(targetShape).getMember(); writer.openBlock("for i := range $L {", "}", operand, () -> { // Only set non-empty non-nil header values String indexedOperand = operand + "[i]"; GoValueAccessUtils.writeIfNonZeroValue(context.getModel(), writer, collectionMemberShape, indexedOperand, false, false, () -> { String op = conditionallyEscapeHeader(context, writer, collectionMemberShape, indexedOperand); writeHttpBindingSetter(context, writer, collectionMemberShape, location, op, (w, s) -> { w.writeInline("$L.AddHeader($L).$L", dest, locationName, s); }); }); }); } private String conditionallyEscapeHeader( GenerationContext context, GoWriter writer, MemberShape memberShape, String operand ) { var targetShape = context.getModel().expectShape(memberShape.getTarget()); if (!targetShape.isStringShape()) { return operand; } if (targetShape.hasTrait(MediaTypeTrait.class)) { return conditionallyBase64Encode(context, writer, targetShape, operand); } writer.pushState(); var returnVar = "escaped"; var pointableIndex = GoPointableIndex.of(context.getModel()); var shouldDereference = pointableIndex.isDereferencable(memberShape); if (shouldDereference) { operand = CodegenUtils.getAsValueIfDereferencable(pointableIndex, memberShape, operand); writer.putContext("escapedVar", "escapedVal"); returnVar = "escapedPtr"; } else { writer.putContext("escapedVar", returnVar); } writer.putContext("returnVar", returnVar); if (targetShape.hasTrait(EnumTrait.class)) { operand = "string(" + operand + ")"; } writer.putContext("value", operand); writer.putContext("quoteValue", SymbolUtils.createValueSymbolBuilder( "Quote", SmithyGoDependency.STRCONV).build()); writer.putContext("indexOf", SymbolUtils.createValueSymbolBuilder( "Index", SmithyGoDependency.STRINGS).build()); writer.putContext("ptrString", SymbolUtils.createValueSymbolBuilder( "String", SmithyGoDependency.SMITHY_PTR).build()); writer.write(""" $escapedVar:L := $value:L if $indexOf:T($value:L, `,`) != -1 || $indexOf:T($value:L, `"`) != -1 { $escapedVar:L = $quoteValue:T($value:L) } """); if (shouldDereference) { writer.write("$returnVar:L := $ptrString:T($escapedVar:L)"); } writer.popState(); return returnVar; } private String conditionallyBase64Encode( GenerationContext context, GoWriter writer, Shape targetShape, String operand ) { // MediaType strings written to headers must be base64 encoded if (!targetShape.isStringShape() || !targetShape.hasTrait(MediaTypeTrait.class)) { return operand; } writer.pushState(); var returnVar = "encoded"; var pointableIndex = GoPointableIndex.of(context.getModel()); var shouldDereference = pointableIndex.isDereferencable(targetShape); if (shouldDereference) { operand = CodegenUtils.getAsValueIfDereferencable(pointableIndex, targetShape, operand); writer.putContext("encodedVar", "encodedVal"); returnVar = "encodedPtr"; } else { writer.putContext("encodedVar", returnVar); } writer.putContext("returnVar", returnVar); writer.putContext("value", operand); writer.putContext("ptrString", SymbolUtils.createValueSymbolBuilder( "String", SmithyGoDependency.SMITHY_PTR).build()); writer.putContext("encodeToString", SymbolUtils.createValueSymbolBuilder( "StdEncoding.EncodeToString", SmithyGoDependency.BASE64).build()); writer.write("$encodedVar:L := $encodeToString:T([]byte($value:L))"); if (shouldDereference) { writer.write("$returnVar:L := $ptrString:T($encodedVar:L)"); } writer.popState(); return returnVar; } /** * Generates serialization functions for shapes in the passed set. These functions * should return a value that can then be serialized by the implementation of * {@code serializeInputDocument}. * * @param context The generation context. * @param shapes The shapes to generate serialization for. */ protected abstract void generateDocumentBodyShapeSerializers(GenerationContext context, Set shapes); @Override public void generateResponseDeserializers(GenerationContext context) { deserializerOverrides.putAll( context.getIntegrations().stream() .flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream()) .flatMap(it -> it.getShapeDeserializers().entrySet().stream()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) ); EventStreamIndex streamIndex = EventStreamIndex.of(context.getModel()); for (OperationShape operation : getHttpBindingOperations(context)) { generateOperationDeserializerMiddleware(context, operation); generateHttpBindingDeserializer(context, operation); Optional streamInfo = streamIndex.getOutputInfo(operation); if (!CodegenUtils.isStubSynthetic(ProtocolUtils.expectOutput(context.getModel(), operation)) && streamInfo.isEmpty()) { generateOperationDocumentDeserializer(context, operation); addOperationDocumentShapeBindersForDeserializer(context, operation); } } GoEventStreamIndex goEventStreamIndex = GoEventStreamIndex.of(context.getModel()); goEventStreamIndex.getOutputEventStreams(context.getService()).ifPresent(shapeIdSetMap -> shapeIdSetMap.forEach((shapeId, eventStreamInfos) -> { generateEventStreamDeserializers(context, context.getModel().expectShape(shapeId, UnionShape.class), eventStreamInfos); })); for (StructureShape error : deserializingErrorShapes) { generateHttpBindingDeserializer(context, error); } } // Generates Http Binding shape deserializer function. private void generateHttpBindingDeserializer(GenerationContext context, Shape shape) { SymbolProvider symbolProvider = context.getSymbolProvider(); Model model = context.getModel(); GoWriter writer = context.getWriter().get(); HttpBindingIndex bindingIndex = model.getKnowledge(HttpBindingIndex.class); List bindings = bindingIndex.getResponseBindings(shape).values().stream() .filter(binding -> isRestBinding(binding.getLocation())) .sorted(Comparator.comparing(HttpBinding::getMember)) .collect(Collectors.toList()); // Don't generate anything if there are no bindings. if (bindings.size() == 0) { return; } Shape targetShape = shape; if (shape.isOperationShape()) { targetShape = ProtocolUtils.expectOutput(model, shape.asOperationShape().get()); } Symbol targetSymbol = symbolProvider.toSymbol(targetShape); Symbol smithyHttpResponsePointableSymbol = SymbolUtils.createPointableSymbolBuilder( "Response", SmithyGoDependency.SMITHY_HTTP_TRANSPORT).build(); writer.addUseImports(SmithyGoDependency.FMT); String functionName = ProtocolGenerator.getOperationHttpBindingsDeserFunctionName( targetShape, context.getService(), getProtocolName()); writer.openBlock("func $L(v $P, response $P) error {", "}", functionName, targetSymbol, smithyHttpResponsePointableSymbol, () -> { writer.openBlock("if v == nil {", "}", () -> { writer.write("return fmt.Errorf(\"unsupported deserialization for nil %T\", v)"); }); writer.write(""); for (HttpBinding binding : bindings) { writeRestDeserializerMember(context, writer, binding); writer.write(""); } writer.write("return nil"); }); } private String generateHttpHeaderValue( GenerationContext context, GoWriter writer, MemberShape memberShape, HttpBinding binding, String operand ) { Shape targetShape = context.getModel().expectShape(memberShape.getTarget()); if (targetShape.getType() != ShapeType.LIST && targetShape.getType() != ShapeType.SET) { writer.addUseImports(SmithyGoDependency.STRINGS); writer.write("$L = strings.TrimSpace($L)", operand, operand); } String value = ""; switch (targetShape.getType()) { case STRING: if (targetShape.hasTrait(EnumTrait.class)) { value = String.format("types.%s(%s)", targetShape.getId().getName(), operand); return value; } // MediaType strings must be base-64 encoded when sent in headers. if (targetShape.hasTrait(MediaTypeTrait.class)) { writer.addUseImports(SmithyGoDependency.BASE64); writer.write("b, err := base64.StdEncoding.DecodeString($L)", operand); writer.write("if err != nil { return err }"); return "string(b)"; } return operand; case ENUM: value = String.format("types.%s(%s)", targetShape.getId().getName(), operand); return value; case BOOLEAN: writer.addUseImports(SmithyGoDependency.STRCONV); writer.write("vv, err := strconv.ParseBool($L)", operand); writer.write("if err != nil { return err }"); return "vv"; case TIMESTAMP: writer.addUseImports(SmithyGoDependency.SMITHY_TIME); HttpBindingIndex bindingIndex = context.getModel().getKnowledge(HttpBindingIndex.class); TimestampFormatTrait.Format format = bindingIndex.determineTimestampFormat( memberShape, binding.getLocation(), Format.HTTP_DATE ); switch (format) { case EPOCH_SECONDS: writer.addUseImports(SmithyGoDependency.STRCONV); writer.write("f, err := strconv.ParseFloat($L, 64)", operand); writer.write("if err != nil { return err }"); writer.write("t := smithytime.ParseEpochSeconds(f)"); break; case HTTP_DATE: writer.write("t, err := smithytime.ParseHTTPDate($L)", operand); writer.write("if err != nil { return err }"); break; case DATE_TIME: writer.write("t, err := smithytime.ParseDateTime($L)", operand); writer.write("if err != nil { return err }"); break; default: throw new CodegenException("Unexpected timestamp format " + format); } return "t"; case BYTE: writer.addUseImports(SmithyGoDependency.STRCONV); writer.write("vv, err := strconv.ParseInt($L, 0, 8)", operand); writer.write("if err != nil { return err }"); return "int8(vv)"; case SHORT: writer.addUseImports(SmithyGoDependency.STRCONV); writer.write("vv, err := strconv.ParseInt($L, 0, 16)", operand); writer.write("if err != nil { return err }"); return "int16(vv)"; case INTEGER: case INT_ENUM: writer.addUseImports(SmithyGoDependency.STRCONV); writer.write("vv, err := strconv.ParseInt($L, 0, 32)", operand); writer.write("if err != nil { return err }"); return "int32(vv)"; case LONG: writer.addUseImports(SmithyGoDependency.STRCONV); writer.write("vv, err := strconv.ParseInt($L, 0, 64)", operand); writer.write("if err != nil { return err }"); return "vv"; case FLOAT: writer.addUseImports(SmithyGoDependency.STRCONV); writer.write("vv, err := strconv.ParseFloat($L, 32)", operand); writer.write("if err != nil { return err }"); return "float32(vv)"; case DOUBLE: writer.addUseImports(SmithyGoDependency.STRCONV); writer.write("vv, err := strconv.ParseFloat($L, 64)", operand); writer.write("if err != nil { return err }"); return "vv"; case BIG_INTEGER: writer.addUseImports(SmithyGoDependency.BIG); writer.write("i := big.NewInt(0)"); writer.write("bi, ok := i.SetString($L,0)", operand); writer.openBlock("if !ok {", "}", () -> { writer.write( "return fmt.Error($S)", "Incorrect conversion from string to BigInteger type" ); }); return "*bi"; case BIG_DECIMAL: writer.addUseImports(SmithyGoDependency.BIG); writer.write("f := big.NewFloat(0)"); writer.write("bd, ok := f.SetString($L,0)", operand); writer.openBlock("if !ok {", "}", () -> { writer.write( "return fmt.Error($S)", "Incorrect conversion from string to BigDecimal type" ); }); return "*bd"; case BLOB: writer.addUseImports(SmithyGoDependency.BASE64); writer.write("b, err := base64.StdEncoding.DecodeString($L)", operand); writer.write("if err != nil { return err }"); return "b"; case SET: case LIST: // handle list/Set as target shape MemberShape targetValueListMemberShape = CodegenUtils.expectCollectionShape(targetShape).getMember(); return getHttpHeaderCollectionDeserializer(context, writer, targetValueListMemberShape, binding, operand); default: throw new CodegenException("unexpected shape type " + targetShape.getType()); } } private String getHttpHeaderCollectionDeserializer( GenerationContext context, GoWriter writer, MemberShape memberShape, HttpBinding binding, String operand ) { writer.write("var list []$P", context.getSymbolProvider().toSymbol(memberShape)); String operandValue = operand + "Val"; writer.openBlock("for _, $L := range $L {", "}", operandValue, operand, () -> { String value = generateHttpHeaderValue(context, writer, memberShape, binding, operandValue); writer.write("list = append(list, $L)", CodegenUtils.getAsPointerIfPointable(context.getModel(), writer, GoPointableIndex.of(context.getModel()), memberShape, value)); }); return "list"; } private void writeRestDeserializerMember( GenerationContext context, GoWriter writer, HttpBinding binding ) { MemberShape memberShape = binding.getMember(); Shape targetShape = context.getModel().expectShape(memberShape.getTarget()); String memberName = context.getSymbolProvider().toMemberName(memberShape); switch (binding.getLocation()) { case HEADER: writeHeaderDeserializerFunction(context, writer, memberName, memberShape, binding); break; case PREFIX_HEADERS: if (!targetShape.isMapShape()) { throw new CodegenException("unexpected prefix-header shape type found in Http bindings"); } writePrefixHeaderDeserializerFunction(context, writer, memberName, memberShape, binding); break; case RESPONSE_CODE: writer.addUseImports(SmithyGoDependency.SMITHY_PTR); writer.write("v.$L = $L", memberName, CodegenUtils.getAsPointerIfPointable(context.getModel(), writer, GoPointableIndex.of(context.getModel()), memberShape, "int32(response.StatusCode)")); break; default: throw new CodegenException("unexpected http binding found"); } } private void writeHeaderDeserializerFunction( GenerationContext context, GoWriter writer, String memberName, MemberShape memberShape, HttpBinding binding ) { writer.openBlock("if headerValues := response.Header.Values($S); len(headerValues) != 0 {", "}", binding.getLocationName(), () -> { var target = memberShape.getTarget(); Shape targetShape = context.getModel().expectShape(target); String operand = "headerValues"; operand = writeHeaderValueAccessor(context, writer, targetShape, binding, operand); if (deserializerOverrides.containsKey(target)) { writer.write(""" deserOverride, err := $T($L) if err != nil { return err } v.$L = deserOverride """, deserializerOverrides.get(target), operand, memberName); return; } var value = generateHttpHeaderValue(context, writer, memberShape, binding, operand); writer.write("v.$L = $L", memberName, CodegenUtils.getAsPointerIfPointable(context.getModel(), writer, GoPointableIndex.of(context.getModel()), memberShape, value)); }); } private void writePrefixHeaderDeserializerFunction( GenerationContext context, GoWriter writer, String memberName, MemberShape memberShape, HttpBinding binding ) { String prefix = binding.getLocationName(); Shape targetShape = context.getModel().expectShape(memberShape.getTarget()); MemberShape valueMemberShape = targetShape.asMapShape() .orElseThrow(() -> new CodegenException("prefix headers must target map shape")) .getValue(); writer.openBlock("for headerKey, headerValues := range response.Header {", "}", () -> { writer.addUseImports(SmithyGoDependency.STRINGS); Symbol targetSymbol = context.getSymbolProvider().toSymbol(targetShape); writer.openBlock( "if lenPrefix := len($S); " + "len(headerKey) >= lenPrefix && strings.EqualFold(headerKey[:lenPrefix], $S) {", "}", prefix, prefix, () -> { writer.openBlock("if v.$L == nil {", "}", memberName, () -> { writer.write("v.$L = $P{}", memberName, targetSymbol); }); String operand = "headerValues"; operand = writeHeaderValueAccessor(context, writer, targetShape, binding, operand); String value = generateHttpHeaderValue(context, writer, valueMemberShape, binding, operand); writer.write("v.$L[strings.ToLower(headerKey[lenPrefix:])] = $L", memberName, CodegenUtils.getAsPointerIfPointable(context.getModel(), writer, GoPointableIndex.of(context.getModel()), valueMemberShape, value)); }); }); } /** * Returns the header value accessor operand, and also if the target shape is a list/set will write the splitting * of the header values by comma(,) utility helper. * * @param context generation context * @param writer writer * @param targetShape target shape * @param binding http binding location * @param operand operand of the header values. * @return returns operand for accessing the header values */ private String writeHeaderValueAccessor( GenerationContext context, GoWriter writer, Shape targetShape, HttpBinding binding, String operand ) { switch (targetShape.getType()) { case LIST: case SET: writerHeaderListValuesSplit(context, writer, CodegenUtils.expectCollectionShape(targetShape), binding, operand); break; default: // Always use first element in header, ignores if there are multiple headers with this key. operand += "[0]"; break; } return operand; } /** * Writes the utility to split split comma separate header values into a single list for consistent iteration. Also * has special case handling for HttpDate timestamp format when serialized as a header list. Assigns the split * header values back to the same operand name. * * @param context generation context * @param writer writer * @param shape target collection shape * @param binding http binding location * @param operand operand of the header values. */ private void writerHeaderListValuesSplit( GenerationContext context, GoWriter writer, CollectionShape shape, HttpBinding binding, String operand ) { writer.openBlock("{", "}", () -> { writer.write("var err error"); writer.addUseImports(SmithyGoDependency.SMITHY_HTTP_TRANSPORT); if (isHttpDateTimestamp(context.getModel(), binding.getLocation(), shape.getMember())) { writer.write("$L, err = smithyhttp.SplitHTTPDateTimestampHeaderListValues($L)", operand, operand); } else { writer.write("$L, err = smithyhttp.SplitHeaderListValues($L)", operand, operand); } writer.openBlock("if err != nil {", "}", () -> { writer.write("return err"); }); }); } @Override public void generateSharedDeserializerComponents(GenerationContext context) { deserializingErrorShapes.forEach(error -> generateErrorDeserializer(context, error)); deserializeDocumentBindingShapes.addAll(ProtocolUtils.resolveRequiredDocumentShapeSerde( context.getModel(), deserializeDocumentBindingShapes)); generateDocumentBodyShapeDeserializers(context, deserializeDocumentBindingShapes); } /** * Adds the top-level shapes from the operation that bind to the body document that require deserializer functions. * * @param context the generator context * @param operation the operation to add document binders from */ private void addOperationDocumentShapeBindersForDeserializer(GenerationContext context, OperationShape operation) { Model model = context.getModel(); HttpBindingIndex httpBindingIndex = HttpBindingIndex.of(model); addDocumentDeserializerBindingShapes(model, httpBindingIndex, operation); for (ShapeId errorShapeId : operation.getErrors()) { addDocumentDeserializerBindingShapes(model, httpBindingIndex, errorShapeId); } } /** * Adds shapes from provided shape that require document deserializer functions to be generated. * * @param model the smithy model. * @param index the http binding index * @param shape the shape to enumerate member shapes for document deserializers */ private void addDocumentDeserializerBindingShapes(Model model, HttpBindingIndex index, ToShapeId shape) { // Walk and add members shapes to the list that will require deserializer functions for (HttpBinding binding : index.getResponseBindings(shape).values()) { MemberShape memberShape = binding.getMember(); Shape targetShape = model.expectShape(memberShape.getTarget()); // Event Stream Member should not immediately generate a document deserializer // and is handled via generateOperationEventMessageDeserializers. if (StreamingTrait.isEventStream(model, memberShape)) { continue; } // Add deserializer helpers for document and payload shape bindings if the operation does not have // any output event streams. if (requiresDocumentSerdeFunction(targetShape) && (binding.getLocation() == HttpBinding.Location.DOCUMENT || binding.getLocation() == HttpBinding.Location.PAYLOAD)) { deserializeDocumentBindingShapes.add(targetShape); } } } /** * Generates the operation document deserializer function. * * @param context the generation context * @param operation the operation shape being generated */ protected abstract void generateOperationDocumentDeserializer(GenerationContext context, OperationShape operation); /** * Generates deserialization functions for shapes in the provided set. These functions * should return a value that can then be deserialized by the implementation of * {@code deserializeOutputDocument}. * * @param context The generation context. * @param shapes The shapes to generate deserialization for. */ protected abstract void generateDocumentBodyShapeDeserializers(GenerationContext context, Set shapes); private void generateErrorDeserializer(GenerationContext context, StructureShape shape) { GoWriter writer = context.getWriter().get(); String functionName = ProtocolGenerator.getErrorDeserFunctionName( shape, context.getService(), context.getProtocolName()); Symbol responseType = getApplicationProtocol().getResponseType(); writer.addUseImports(SmithyGoDependency.BYTES); writer.openBlock("func $L(response $P, errorBody *bytes.Reader) error {", "}", functionName, responseType, () -> deserializeError(context, shape)); writer.write(""); } /** * Writes a function body that deserializes the given error. * *

Two parameters will be available in scope: *

    *
  • {@code response: smithyhttp.HTTPResponse}: the HTTP response received.
  • *
  • {@code errorBody: bytes.BytesReader}: the HTTP response body.
  • *
* * @param context The generation context. * @param shape The error shape. */ protected abstract void deserializeError(GenerationContext context, StructureShape shape); /** * Converts the first letter and any letter following a hyphen to upper case. The remaining letters are lower cased. * * @param key the header * @return the canonical header */ private String getCanonicalHeader(String key) { char[] chars = key.toCharArray(); boolean upper = true; for (int i = 0; i < chars.length; i++) { char c = chars[i]; c = upper ? Character.toUpperCase(c) : Character.toLowerCase(c); chars[i] = c; upper = c == '-'; } return new String(chars); } } HttpProtocolGeneratorUtils.java000066400000000000000000000214301463735525100422170ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator.GenerationContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.HttpBinding; import software.amazon.smithy.model.knowledge.HttpBindingIndex; import software.amazon.smithy.model.shapes.OperationShape; 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; public final class HttpProtocolGeneratorUtils { private HttpProtocolGeneratorUtils() { } /** * Generates a function that handles error deserialization by getting the error code then * dispatching to the error-specific deserializer. *

* If the error code does not map to a known error, a generic error will be returned using * the error code and error message discovered in the response. *

* The default error message and code are both "UnknownError". * * @param context The generation context. * @param operation The operation to generate for. * @param responseType The response type for the HTTP protocol. * @param errorMessageCodeGenerator A consumer that generates a snippet that sets the {@code errorCode} * and {@code errorMessage} variables from the http response. * @return A set of all error structure shapes for the operation that were dispatched to. */ public static Set generateErrorDispatcher( GenerationContext context, OperationShape operation, Symbol responseType, Consumer errorMessageCodeGenerator, BiFunction> operationErrorsToShapes ) { return generateErrorDispatcher( context, operation, responseType, errorMessageCodeGenerator, operationErrorsToShapes, writer -> defaultBlock(writer)); } private static void defaultBlock(GoWriter writer) { writer.openBlock("default:", "", () -> { writer.openBlock("genericError := &smithy.GenericAPIError{", "}", () -> { writer.write("Code: errorCode,"); writer.write("Message: errorMessage,"); }); writer.write("return genericError"); }); } @FunctionalInterface public interface DefaultBlockWriter { void write(GoWriter writer); } /** * Generates a function that handles error deserialization by getting the error code then * dispatching to the error-specific deserializer. Provies an option to write custom default * error block. */ public static Set generateErrorDispatcher( GenerationContext context, OperationShape operation, Symbol responseType, Consumer errorMessageCodeGenerator, BiFunction> operationErrorsToShapes, DefaultBlockWriter defaultBlockWriter ) { GoWriter writer = context.getWriter().get(); ServiceShape service = context.getService(); String protocolName = context.getProtocolName(); Set errorShapes = new TreeSet<>(); String errorFunctionName = ProtocolGenerator.getOperationErrorDeserFunctionName( operation, service, protocolName); writer.addUseImports(SmithyGoDependency.SMITHY_MIDDLEWARE); writer.openBlock("func $L(response $P, metadata *middleware.Metadata) error {", "}", errorFunctionName, responseType, () -> { writer.addUseImports(SmithyGoDependency.BYTES); writer.addUseImports(SmithyGoDependency.IO); // Copy the response body into a seekable type writer.write("var errorBuffer bytes.Buffer"); writer.openBlock("if _, err := io.Copy(&errorBuffer, response.Body); err != nil {", "}", () -> { writer.write("return &smithy.DeserializationError{Err: fmt.Errorf(" + "\"failed to copy error response body, %w\", err)}"); }); writer.write("errorBody := bytes.NewReader(errorBuffer.Bytes())"); writer.write(""); // Set the default values for code and message. writer.write("errorCode := \"UnknownError\""); writer.write("errorMessage := errorCode"); writer.write(""); // Dispatch to the message/code generator to try to get the specific code and message. errorMessageCodeGenerator.accept(context); writer.openBlock("switch {", "}", () -> { operationErrorsToShapes.apply(context, operation).forEach((name, errorId) -> { StructureShape error = context.getModel().expectShape(errorId).asStructureShape().get(); errorShapes.add(error); String errorDeserFunctionName = ProtocolGenerator.getErrorDeserFunctionName( error, service, protocolName); writer.addUseImports(SmithyGoDependency.STRINGS); writer.openBlock("case strings.EqualFold($S, errorCode):", "", name, () -> { writer.write("return $L(response, errorBody)", errorDeserFunctionName); }); }); // Create a generic error writer.addUseImports(SmithyGoDependency.SMITHY); defaultBlockWriter.write(writer); }); }).write(""); return errorShapes; } /** * Returns whether a shape has response bindings for the provided HttpBinding location. * The shape can be an operation shape, error shape or an output shape. * * @param model the model * @param shape the shape with possible presence of response bindings * @param location the HttpBinding location for response binding * @return boolean indicating presence of response bindings in the shape for provided location */ public static boolean isShapeWithResponseBindings(Model model, Shape shape, HttpBinding.Location location) { Collection bindings = HttpBindingIndex.of(model) .getResponseBindings(shape).values(); for (HttpBinding binding : bindings) { if (binding.getLocation() == location) { return true; } } return false; } /** * Returns a map of error names to their {@link ShapeId}. * * @param context the generation context * @param operation the operation shape to retrieve errors for * @return map of error names to {@link ShapeId} */ public static Map getOperationErrors(GenerationContext context, OperationShape operation) { return operation.getErrors().stream() .collect(Collectors.toMap( shapeId -> shapeId.getName(context.getService()), Function.identity(), (x, y) -> { if (!x.equals(y)) { throw new CodegenException(String.format("conflicting error shape ids: %s, %s", x, y)); } return x; }, TreeMap::new)); } } HttpProtocolTestGenerator.java000066400000000000000000000205571463735525100420470ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import static java.lang.String.format; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.logging.Logger; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.ShapeValueGenerator; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator.GenerationContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.OperationIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.protocoltests.traits.HttpMessageTestCase; import software.amazon.smithy.protocoltests.traits.HttpRequestTestCase; 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.utils.IoUtils; /** * Generates protocol unit tests for the HTTP protocol from smithy models. */ public class HttpProtocolTestGenerator { private static final Logger LOGGER = Logger.getLogger(HttpProtocolTestGenerator.class.getName()); private final SymbolProvider symbolProvider; private final GoSettings settings; private final GoWriter writer; private final GoDelegator delegator; private final Model model; private final ServiceShape service; private final String protocolName; private final Set additionalStubs = new TreeSet<>(); private final HttpProtocolUnitTestRequestGenerator.Builder requestTestBuilder; private final HttpProtocolUnitTestResponseGenerator.Builder responseTestBuilder; private final HttpProtocolUnitTestResponseErrorGenerator.Builder responseErrorTestBuilder; /** * Initializes the protocol generator. * * @param context Protocol generation context. * @param requestTestBuilder builder that will create a request test generator. * @param responseTestBuilder build that will create a response test generator. * @param responseErrorTestBuilder builder that will create a response API error test generator. */ public HttpProtocolTestGenerator( GenerationContext context, HttpProtocolUnitTestRequestGenerator.Builder requestTestBuilder, HttpProtocolUnitTestResponseGenerator.Builder responseTestBuilder, HttpProtocolUnitTestResponseErrorGenerator.Builder responseErrorTestBuilder ) { this.settings = context.getSettings(); this.model = context.getModel(); this.service = context.getService(); this.protocolName = context.getProtocolName(); this.symbolProvider = context.getSymbolProvider(); this.writer = context.getWriter().get(); this.delegator = context.getDelegator(); this.requestTestBuilder = requestTestBuilder; this.responseTestBuilder = responseTestBuilder; this.responseErrorTestBuilder = responseErrorTestBuilder; } /** * Generates the API HTTP protocol tests defined in the smithy model. */ public void generateProtocolTests() { OperationIndex operationIndex = model.getKnowledge(OperationIndex.class); TopDownIndex topDownIndex = model.getKnowledge(TopDownIndex.class); for (OperationShape operation : new TreeSet<>(topDownIndex.getContainedOperations(service))) { if (operation.hasTag("server-only")) { continue; } // 1. Generate test cases for each request. operation.getTrait(HttpRequestTestsTrait.class).ifPresent(trait -> { final List testCases = filterProtocolTestCases(trait.getTestCases()); if (testCases.isEmpty()) { return; } delegator.useShapeTestWriter(operation, (writer) -> { LOGGER.fine(() -> format("Generating request protocol test case for %s", operation.getId())); requestTestBuilder.model(model) .symbolProvider(symbolProvider) .protocolName(protocolName) .service(service) .operation(operation) .testCases(trait.getTestCases()) .build() .generateTestFunction(writer); }); }); // 2. Generate test cases for each response. operation.getTrait(HttpResponseTestsTrait.class).ifPresent(trait -> { final List testCases = filterProtocolTestCases(trait.getTestCases()); if (testCases.isEmpty()) { return; } delegator.useShapeTestWriter(operation, (writer) -> { LOGGER.fine(() -> format("Generating response protocol test case for %s", operation.getId())); responseTestBuilder.model(model) .symbolProvider(symbolProvider) .protocolName(protocolName) .service(service) .operation(operation) .testCases(trait.getTestCases()) .shapeValueGeneratorConfig(ShapeValueGenerator.Config.builder() .normalizeHttpPrefixHeaderKeys(true).build()) .build() .generateTestFunction(writer); }); }); // 3. Generate test cases for each error on each operation. for (StructureShape error : operationIndex.getErrors(operation)) { if (error.hasTag("server-only")) { continue; } error.getTrait(HttpResponseTestsTrait.class).ifPresent(trait -> { final List testCases = filterProtocolTestCases(trait.getTestCases()); if (testCases.isEmpty()) { return; } delegator.useShapeTestWriter(operation, (writer) -> { LOGGER.fine(() -> format("Generating response error protocol test case for %s", operation.getId())); responseErrorTestBuilder.model(model) .symbolProvider(symbolProvider) .protocolName(protocolName) .service(service) .operation(operation) .error(error) .testCases(trait.getTestCases()) .build() .generateTestFunction(writer); }); }); } } // Include any additional stubs required. for (String additionalStub : additionalStubs) { writer.write(IoUtils.readUtf8Resource(getClass(), additionalStub)); } } private List filterProtocolTestCases(List testCases) { List filtered = new ArrayList<>(); for (T testCase : testCases) { if (testCase.getProtocol().equals(settings.getProtocol())) { filtered.add(testCase); } } return filtered; } } HttpProtocolUnitTestGenerator.java000066400000000000000000001036341463735525100427050ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.function.Consumer; import java.util.logging.Logger; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.ShapeValueGenerator; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.MemberShape; 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.StructureShape; import software.amazon.smithy.model.traits.IdempotencyTokenTrait; import software.amazon.smithy.protocoltests.traits.AppliesTo; import software.amazon.smithy.protocoltests.traits.HttpMessageTestCase; import software.amazon.smithy.utils.SmithyBuilder; /** * Abstract base implementation for protocol test generators to extend in order to generate HttpMessageTestCase * specific protocol tests. * * @param Specific HttpMessageTestCase the protocol test generator is for. */ public abstract class HttpProtocolUnitTestGenerator { private static final Logger LOGGER = Logger.getLogger(HttpProtocolUnitTestGenerator.class.getName()); protected final GoSettings settings; protected final Model model; protected final SymbolProvider symbolProvider; protected final List testCases; protected final ServiceShape service; protected final OperationShape operation; protected final Symbol opSymbol; protected final StructureShape inputShape; protected final Symbol inputSymbol; protected final StructureShape outputShape; protected final Symbol outputSymbol; protected final String protocolName; protected final Set clientConfigValues = new TreeSet<>(); protected final Set skipTests = new TreeSet<>(); protected final ShapeValueGenerator.Config shapeValueGeneratorConfig; /** * Initializes the abstract protocol tests generator. * * @param builder the builder initializing the generator. */ protected HttpProtocolUnitTestGenerator(Builder builder) { this.settings = SmithyBuilder.requiredState("settings", builder.settings); this.model = SmithyBuilder.requiredState("model", builder.model); this.symbolProvider = SmithyBuilder.requiredState("symbolProvider", builder.symbolProvider); this.protocolName = SmithyBuilder.requiredState("protocolName", builder.protocolName); this.service = SmithyBuilder.requiredState("service", builder.service); this.operation = SmithyBuilder.requiredState("operation", builder.operation); this.testCases = SmithyBuilder.requiredState("testCases", builder.testCases); this.clientConfigValues.addAll(builder.clientConfigValues); this.skipTests.addAll(builder.skipTests); this.shapeValueGeneratorConfig = SmithyBuilder.requiredState("config", builder.shapeValueGeneratorConfig); opSymbol = symbolProvider.toSymbol(operation); inputShape = model.expectShape(operation.getInput() .orElseThrow(() -> new CodegenException("missing input shape for operation: " + operation.getId())), StructureShape.class); inputSymbol = symbolProvider.toSymbol(inputShape); outputShape = model.expectShape(operation.getOutput() .orElseThrow(() -> new CodegenException("missing output shape for operation: " + operation.getId())), StructureShape.class); outputSymbol = symbolProvider.toSymbol(outputShape); } /** * Provides the unit test function's format string. * * @return returns format string paired with unitTestFuncNameArgs */ abstract String unitTestFuncNameFormat(); /** * Provides the unit test function name's format string arguments. * * @return returns a list of arguments used to format the unitTestFuncNameFormat returned format string. */ abstract Object[] unitTestFuncNameArgs(); /** * Hook to provide custom generated code within a test function before test cases are defined. * * @param writer writer to write generated code with. */ protected void generateTestSetup(GoWriter writer) { // Pass } /** * Hook to generate the parameter declarations as struct parameters into the test case's struct definition. * Must generate all test case parameters before returning. * * @param writer writer to write generated code with. */ abstract void generateTestCaseParams(GoWriter writer); /** * Hook to generate all the test case parameters as struct member values for a single test case. * Must generate all test case parameter values before returning. * * @param writer writer to write generated code with. * @param testCase definition of a single test case. */ abstract void generateTestCaseValues(GoWriter writer, T testCase); /** * Hook to optionally generate additional setup needed before the test body is created. * * @param writer writer to write generated code with. */ protected void generateTestBodySetup(GoWriter writer) { // pass } /** * Hook to generate the HTTP response body of the protocol test. If overriding and delegating to this method must * the last usage of ResponseWriter. * * @param writer writer to write generated code with. */ protected void generateTestServerHandler(GoWriter writer) { writer.write("w.WriteHeader(200)"); } /** * Hook to generate the HTTP test server that will receive requests and provide responses back to the requester. * * @param writer writer to write generated code with. * @param name test server variable name * @param handler lambda for writing handling of HTTP request */ protected void generateTestServer(GoWriter writer, String name, Consumer handler) { writer.addUseImports(SmithyGoDependency.NET_HTTP); writer.addUseImports(SmithyGoDependency.NET_HTTP_TEST); writer.openBlock("$L := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){", "}))", name, () -> { handler.accept(writer); }); writer.write("defer $L.Close()", name); writer.write("serverURL := server.URL"); } /** * Hook to generate the instance of the API client for the protocol test. * * @param writer writer to write generated code with. * @param clientName test client variable name */ protected void generateTestClient(GoWriter writer, String clientName) { writer.openBlock("$L := New(Options{", "})", clientName, () -> { for (ConfigValue value : clientConfigValues) { writeStructField(writer, value.getName(), value.getValue()); } }); } /** * Hook to generate the client invoking the API operation of the test. Should not do any assertions. * * @param writer writer to write generated code with. * @param clientName name of the client variable. */ abstract void generateTestInvokeClientOperation(GoWriter writer, String clientName); /** * Hook to generate the assertions for the operation's test cases. Will be in the same scope as the test body. * * @param writer writer to write generated code with. */ abstract void generateTestAssertions(GoWriter writer); /** * Generates the test function for the operation using the provided writer. * * @param writer writer to write generated code with. */ public void generateTestFunction(GoWriter writer) { writer.addUseImports(SmithyGoDependency.TESTING); writer.openBlock("func " + unitTestFuncNameFormat() + "(t *testing.T) {", "}", unitTestFuncNameArgs(), () -> { skipTests.forEach((skipTest) -> { if (skipTest.matches(service.getId(), operation.getId())) { writer.write("t.Skip(\"disabled test $L $L\")", service.getId(), operation.getId()); writer.write(""); } }); generateTestSetup(writer); writer.write("cases := map[string]struct {"); generateTestCaseParams(writer); writer.openBlock("}{", "}", () -> { for (T testCase : testCases) { Optional appliesTo = testCase.getAppliesTo(); if (appliesTo.isPresent() && !(appliesTo.get().equals(AppliesTo.CLIENT))) { continue; } testCase.getDocumentation().ifPresent(writer::writeDocs); writer.openBlock("$S: {", "},", testCase.getId(), () -> { generateTestCaseValues(writer, testCase); }); } }); // And test case iteration/assertions writer.openBlock("for name, c := range cases {", "}", () -> { writer.openBlock("t.Run(name, func(t *testing.T) {", "})", () -> { skipTests.forEach((skipTest) -> { for (T testCase : testCases) { if (skipTest.matches(service.getId(), operation.getId(), testCase.getId())) { writer.openBlock("if name == $S {", "}", testCase.getId(), () -> { writer.write("t.Skip(\"disabled test $L $L\")", service.getId(), operation.getId()); }); writer.write(""); } } }); generateTestBodySetup(writer); generateTestServer(writer, "server", this::generateTestServerHandler); generateTestClient(writer, "client"); generateTestInvokeClientOperation(writer, "client"); generateTestAssertions(writer); }); }); }); } /** * Writes a single Go structure field key and value. * * @param writer writer to write generated code with. * @param field the field name of the struct member. * @param value the value of the struct member. */ protected void writeStructField(GoWriter writer, String field, Object value) { writer.write("$L: $L,", field, value); } /** * Writes a single Go structure field key and value. Provides inline formatting of the field value. * * @param writer writer to write generated code with. * @param field the field name of the struct member. * @param valueFormat the format string to use for the field value * @param args the format string arguments for the field value. */ protected void writeStructField(GoWriter writer, String field, String valueFormat, Object... args) { writer.writeInline("$L: ", field); writer.writeInline(valueFormat, args); writer.write(","); } /** * Writes a single Go structure field key and value. Writes the field value inline from the shape and * ObjectNode graph provided. * * @param writer writer to write generated code with. * @param field the field name of the struct member. * @param shape the shape the field member. * @param params the node of values to fill the member with. */ protected void writeStructField(GoWriter writer, String field, StructureShape shape, ObjectNode params) { writer.writeInline("$L: ", field); writeShapeValueInline(writer, shape, params); writer.write(","); } /** * Writes a single Go structure field key and value. Writes the field value inline from the shape and * ObjectNode graph provided. Value writer is responsible for writing the proceeding comma after the value. * * @param writer writer to write generated code with. * @param field the field name of the struct member. * @param value inline value writer. */ protected void writeStructField(GoWriter writer, String field, Consumer value) { writer.writeInline("$L: ", field); value.accept(writer); } /** * Writes a Go structure field for a QueryItem value. * * @param writer writer to write generated code with. * @param field the name of the field. * @param values list of values for the query. */ protected void writeQueryItemsStructField(GoWriter writer, String field, List values) { writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); writer.openBlock("$L: []smithytesting.QueryItem {", "},", field, () -> { writeQueryItemsValues(writer, values); }); } /** * Writes values of query items as slice members. * * @param writer writer to write generated code with. * @param values list of values for the query. */ protected void writeQueryItemsValues(GoWriter writer, List values) { for (String item : values) { String[] parts = item.split("=", 2); String value = ""; if (parts.length > 1) { value = parts[1]; } writer.write("{Key: $S, Value: $S},", parts[0], value); } } /** * Writes utility to breakout RawQuery string into its components for testing. * * @param writer writer to write generated code with. * @param source name of variable containing raw query string. * @param target name of destination variable that will be created to hold QueryItems */ protected void writeQueryItemBreakout(GoWriter writer, String source, String target) { writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); writer.write("$L := smithytesting.ParseRawQuery($L)", target, source); } /** * Writes a structure header member with values from a map. * * @param writer writer to write generated code with. * @param field name of the field. * @param values map of header key and value pairs. */ protected void writeHeaderStructField(GoWriter writer, String field, Map values) { if (values.size() == 0) { return; } writer.openBlock("$L: http.Header{", "},", field, () -> { writeHeaderValues(writer, values); }); } /** * Writes individual header key value field pairs. * * @param writer writer to write generated code with. * @param values map of header key/value pairs. */ protected void writeHeaderValues(GoWriter writer, Map values) { values.forEach((k, v) -> { writer.write("$S: []string{$S},", k, v); }); } /** * Writes a string slice to a struct field. * * @param writer writer to write generated code with. * @param field the name of the field. * @param values the list of field values. */ protected void writeStringSliceStructField(GoWriter writer, String field, List values) { if (values.size() == 0) { return; } writer.openBlock("$L: []string{", "},", field, () -> { writeStringSliceValues(writer, values); }); } /** * Writes a list of strings as go string slice members. * * @param writer writer to write generated code with. * @param values the list of string values. */ protected void writeStringSliceValues(GoWriter writer, List values) { for (String value : values) { writer.write("$S,", value); } } /** * Writes the assertion for comparing two scalar values. * * @param writer writer to write generated code with. * @param expect variable name of the expected value. * @param actual variable name of the actual value. * @param tag additional error message description. */ protected void writeAssertScalarEqual(GoWriter writer, String expect, String actual, String tag) { writer.openBlock("if e, a := $L, $L; e != a {", "}", expect, actual, () -> { writer.write("t.Errorf(\"expect %v $L, got %v\", e, a)", tag); }); } /** * Writes the assertion for comparing two complex type values, e.g. structures. * * @param writer writer to write generated code with. * @param expect the variable name of the expected value. * @param actual the variable name of the actual value. * @param ignoreTypes list of type values that should be ignored by the compare. */ protected void writeAssertComplexEqual( GoWriter writer, String expect, String actual, String[] ignoreTypes ) { writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); writer.openBlock("if err := smithytesting.CompareValues($L, $L); err != nil {", "}", expect, actual, () -> { writer.write("t.Errorf(\"expect $L value match:\\n%v\", err)", expect); }); } /** * Writes assertion that a variable's value must be nil. * * @param writer writer to write generated code with. * @param field the variable name of the value. */ protected void writeAssertNil(GoWriter writer, String field) { writer.openBlock("if $L != nil {", "}", field, () -> { writer.write("t.Fatalf(\"expect nil $L, got %v\", $L)", field, field); }); } /** * Writes the assertion that a variable must not be nil. * * @param writer writer to write generated code with. * @param field the variable name of the value. */ protected void writeAssertNotNil(GoWriter writer, String field) { writer.openBlock("if $L == nil {", "}", field, () -> { writer.write("t.Fatalf(\"expect not nil $L\")", field); }); } /** * Writes the assertion that query contains expected values. * * @param writer writer to write generated code with. * @param expect variable name with the expected values. * @param actual variable name with the actual values. */ void writeAssertHasQuery(GoWriter writer, String expect, String actual) { writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); writer.write("smithytesting.AssertHasQuery(t, $L, $L)", expect, actual); } /** * Writes the assertion that an query contains keys. * * @param writer writer to write generated code with. * @param expect variable name with the expected values. * @param actual variable name with the actual values. */ protected void writeAssertRequireQuery(GoWriter writer, String expect, String actual) { writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); writer.write("smithytesting.AssertHasQueryKeys(t, $L, $L)", expect, actual); } /** * Writes the assertion that an query must not contain keys. * * @param writer writer to write generated code with. * @param expect variable name with the expected values. * @param actual variable name with the actual values. */ protected void writeAssertForbidQuery(GoWriter writer, String expect, String actual) { writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); writer.write("smithytesting.AssertNotHaveQueryKeys(t, $L, $L)", expect, actual); } /** * Writes the assertion that headers contain expected values. * * @param writer writer to write generated code with. * @param expect variable name with the expected values. * @param actual variable name with the actual values. */ protected void writeAssertHasHeader(GoWriter writer, String expect, String actual) { writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); writer.write("smithytesting.AssertHasHeader(t, $L, $L)", expect, actual); } /** * Writes the assertion that the header contains keys. * * @param writer writer to write generated code with. * @param expect variable name with the expected values. * @param actual variable name with the actual values. */ protected void writeAssertRequireHeader(GoWriter writer, String expect, String actual) { writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); writer.write("smithytesting.AssertHasHeaderKeys(t, $L, $L)", expect, actual); } /** * Writes the assertion that the header must not contain keys. * * @param writer writer to write generated code with. * @param expect variable name with the expected values. * @param actual variable name with the actual values. */ protected void writeAssertForbidHeader(GoWriter writer, String expect, String actual) { writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); writer.write("smithytesting.AssertNotHaveHeaderKeys(t, $L, $L)", expect, actual); } /** * Writes a shape type declaration value filled with values in the ObjectNode. * * @param writer writer to write generated code with. * @param shape shape of the value type to be created. * @param params values to initialize shape type with. */ protected void writeShapeValueInline(GoWriter writer, StructureShape shape, ObjectNode params) { new ShapeValueGenerator(settings, model, symbolProvider, shapeValueGeneratorConfig) .writePointableStructureShapeValueInline(writer, shape, params); } /** * Returns if the operation has an idempotency token input member. * * @return if the operation has an idempotency token input member. */ private boolean hasIdempotencyTokenInputMember() { for (MemberShape member : inputShape.members()) { if (member.hasTrait(IdempotencyTokenTrait.class)) { return true; } } return false; } public abstract static class Builder { protected GoSettings settings; protected Model model; protected SymbolProvider symbolProvider; protected String protocolName = ""; protected ServiceShape service; protected OperationShape operation; protected List testCases = new ArrayList<>(); protected Set clientConfigValues = new TreeSet<>(); protected Set skipTests = new TreeSet<>(); protected ShapeValueGenerator.Config shapeValueGeneratorConfig = ShapeValueGenerator.Config.builder().build(); public Builder settings(GoSettings settings) { this.settings = settings; return this; } public Builder model(Model model) { this.model = model; return this; } public Builder symbolProvider(SymbolProvider symbolProvider) { this.symbolProvider = symbolProvider; return this; } public Builder protocolName(String protocolName) { this.protocolName = protocolName; return this; } public Builder service(ServiceShape service) { this.service = service; return this; } public Builder operation(OperationShape operation) { this.operation = operation; return this; } public Builder testCases(List testCases) { this.testCases.clear(); return this.addTestCases(testCases); } public Builder addTestCases(List testCases) { this.testCases.addAll(testCases); return this; } public Builder clientConfigValue(ConfigValue configValue) { this.clientConfigValues.add(configValue); return this; } public Builder clientConfigValues(Set clientConfigValues) { this.clientConfigValues.clear(); return this.addClientConfigValues(clientConfigValues); } public Builder addClientConfigValues(Set clientConfigValues) { this.clientConfigValues.addAll(clientConfigValues); return this; } public Builder skipTest(SkipTest skipTest) { this.skipTests.add(skipTest); return this; } public Builder skipTests(Set skipTests) { this.skipTests.clear(); return this.addSkipTests(skipTests); } public Builder addSkipTests(Set skipTests) { this.skipTests.addAll(skipTests); return this; } public Builder shapeValueGeneratorConfig(ShapeValueGenerator.Config config) { this.shapeValueGeneratorConfig = config; return this; } abstract HttpProtocolUnitTestGenerator build(); } /** * Represents a test client option configuration value. */ public static class ConfigValue implements Comparable { private final String name; private final Consumer value; ConfigValue(Builder builder) { this.name = SmithyBuilder.requiredState("name", builder.name); this.value = SmithyBuilder.requiredState("value", builder.value); } /** * Get the config field name. * * @return the field name */ public String getName() { return name; } /** * Get the inline value writer for the field. * * @return the inline value writer */ public Consumer getValue() { return value; } public static Builder builder() { return new Builder(); } @Override public int compareTo(ConfigValue o) { return getName().compareTo(o.getName()); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ConfigValue that = (ConfigValue) o; return Objects.equals(getName(), that.getName()) && Objects.equals(getValue(), that.getValue()); } @Override public int hashCode() { return Objects.hash(name, value); } /** * Builder for {@link ConfigValue}. */ public static final class Builder implements SmithyBuilder { private String name; private Consumer value; private Builder() { } /** * Set the name of the field. * * @param name field name * @return the builder */ public Builder name(String name) { this.name = name; return this; } /** * Set the inline value writer. * * @param value the inline value writer * @return the builder */ public Builder value(Consumer value) { this.value = value; return this; } @Override public ConfigValue build() { return new ConfigValue(this); } } } /** * Represents a test tests that should be skipped. */ public static class SkipTest implements Comparable { private final ShapeId service; private final ShapeId operation; private final List testNames; SkipTest(Builder builder) { this.service = SmithyBuilder.requiredState("service id", builder.service); this.operation = SmithyBuilder.requiredState("operation id", builder.operation); this.testNames = builder.testNames; } /** * Get the service the skip test applies to. * * @return the service id */ public ShapeId getService() { return service; } /** * Get the operation the skip test applies to. * * @return the operation id */ public ShapeId getOperation() { return operation; } /** * Get the names of the tests the skip test applies to. * * @return the name of the test to skip */ public List getTestNames() { return testNames; } /** * Returns if the skip test case matches the test being evaluated. If a test name isn't specified in the skip * test only the service and operation are considered for matches. * * @param service id of the service * @param operation id of the operation * @param testName name of the test * @return if the skip test matches */ public boolean matches(ShapeId service, ShapeId operation, String testName) { if (!this.service.equals(service)) { return false; } if (!this.operation.equals(operation)) { return false; } // SkipTests not for specific test should not match this check. if (this.testNames.isEmpty()) { return false; } return this.testNames.contains(testName); } /** * Returns if the skip test matches the service and operation, with no individual test defined. If an individual * test name is specified the skip test will not match. * * @param service id of the service * @param operation id of the operation * @return if the skip test matches */ public boolean matches(ShapeId service, ShapeId operation) { if (!this.service.equals(service)) { return false; } if (!this.operation.equals(operation)) { return false; } // SkipTests for specific test should not match this check. return this.testNames.isEmpty(); } public static Builder builder() { return new Builder(); } @Override public int compareTo(SkipTest o) { return Comparator.comparing(SkipTest::getService) .thenComparing(SkipTest::getOperation) .compare(this, o); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SkipTest that = (SkipTest) o; return Objects.equals(getService(), that.getService()) && Objects.equals(getOperation(), that.getOperation()) && Objects.equals(getTestNames(), that.getTestNames()); } @Override public int hashCode() { return Objects.hash(service, operation, testNames); } /** * Builder for {@link SkipTest}. */ public static final class Builder implements SmithyBuilder { private ShapeId service; private ShapeId operation; private List testNames = new ArrayList<>(); private Builder() { } /** * Set the service of the test. * * @param service field service * @return the builder */ public Builder service(ShapeId service) { this.service = service; return this; } /** * Set the operation of the test. * * @param operation is the operation of the test to skip * @return the builder */ public Builder operation(ShapeId operation) { this.operation = operation; return this; } /** * Set the name of the test to skip. * * @param testName is the name of the test to skip * @return the builder */ public Builder addTestName(String testName) { this.testNames.add(testName); return this; } @Override public SkipTest build() { return new SkipTest(this); } } } } HttpProtocolUnitTestRequestGenerator.java000066400000000000000000000351551463735525100442600ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. * * */ package software.amazon.smithy.go.codegen.integration; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.SmithyGoTypes.Private.RequestCompression.AddCaptureUncompressedRequest; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; import java.util.logging.Logger; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.traits.RequestCompressionTrait; import software.amazon.smithy.protocoltests.traits.HttpRequestTestCase; import software.amazon.smithy.utils.MapUtils; /** * Generates HTTP protocol unit tests for HTTP request test cases. */ public class HttpProtocolUnitTestRequestGenerator extends HttpProtocolUnitTestGenerator { private static final Logger LOGGER = Logger.getLogger(HttpProtocolUnitTestRequestGenerator.class.getName()); private static final Set ALLOWED_ALGORITHMS = new HashSet<>(Arrays.asList("gzip")); /** * Initializes the protocol test generator. * * @param builder the builder initializing the generator. */ protected HttpProtocolUnitTestRequestGenerator(Builder builder) { super(builder); } /** * Provides the unit test function's format string. * * @return returns format string paired with unitTestFuncNameArgs */ @Override protected String unitTestFuncNameFormat() { return "TestClient_$L_$LSerialize"; } /** * Provides the unit test function name's format string arguments. * * @return returns a list of arguments used to format the unitTestFuncNameFormat returned format string. */ @Override protected Object[] unitTestFuncNameArgs() { return new Object[]{opSymbol.getName(), protocolName}; } /** * Hook to generate the parameter declarations as struct parameters into the test case's struct definition. * Must generate all test case parameters before returning. * * @param writer writer to write generated code with. */ @Override protected void generateTestCaseParams(GoWriter writer) { writer.write("Params $P", inputSymbol); // TODO authScheme writer.write("ExpectMethod string"); writer.write("ExpectURIPath string"); writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); writer.write("ExpectQuery []smithytesting.QueryItem"); writer.write("RequireQuery []string"); writer.write("ForbidQuery []string"); writer.addUseImports(SmithyGoDependency.NET_HTTP); writer.write("ExpectHeader http.Header"); writer.write("RequireHeader []string"); writer.write("ForbidHeader []string"); writer.write("Host $P", SymbolUtils.createPointableSymbolBuilder("URL", SmithyGoDependency.NET_URL).build()); writer.write("BodyMediaType string"); writer.addUseImports(SmithyGoDependency.IO); writer.write("BodyAssert func(io.Reader) error"); } /** * Hook to generate all the test case parameters as struct member values for a single test case. * Must generate all test case parameter values before returning. * * @param writer writer to write generated code with. * @param testCase definition of a single test case. */ @Override protected void generateTestCaseValues(GoWriter writer, HttpRequestTestCase testCase) { writeStructField(writer, "Params", inputShape, testCase.getParams()); writeStructField(writer, "ExpectMethod", "$S", testCase.getMethod()); writeStructField(writer, "ExpectURIPath", "$S", testCase.getUri()); writeQueryItemsStructField(writer, "ExpectQuery", testCase.getQueryParams()); writeStringSliceStructField(writer, "RequireQuery", testCase.getRequireQueryParams()); writeStringSliceStructField(writer, "ForbidQuery", testCase.getForbidQueryParams()); writeHeaderStructField(writer, "ExpectHeader", testCase.getHeaders()); writeStringSliceStructField(writer, "RequireHeader", testCase.getRequireHeaders()); writeStringSliceStructField(writer, "ForbidHeader", testCase.getForbidHeaders()); if (testCase.getHost().isPresent()) { writeStructField(writer, "Host", (w) -> { var hostValue = testCase.getHost() .orElse(""); if (hostValue.length() == 0) { w.write("nil,"); return; } if (hostValue.split("://", 2).length == 1) { hostValue = "https://" + hostValue; } w.pushState(); w.putContext("url", SymbolUtils.createPointableSymbolBuilder("URL", SmithyGoDependency.NET_URL).build()); w.putContext("parse", SymbolUtils.createPointableSymbolBuilder("Parse", SmithyGoDependency.NET_URL).build()); w.putContext("host", hostValue); w.write(""" func () $url:P { host := $host:S if len(host) == 0 { return nil } u, err := $parse:T(host) if err != nil { panic(err) } return u }(),"""); w.popState(); }); } String bodyMediaType = ""; if (testCase.getBodyMediaType().isPresent()) { bodyMediaType = testCase.getBodyMediaType().get(); writeStructField(writer, "BodyMediaType", "$S", bodyMediaType); } if (testCase.getBody().isPresent()) { String body = testCase.getBody().get(); writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); writer.addUseImports(SmithyGoDependency.IO); if (body.length() == 0) { writeStructField(writer, "BodyAssert", "func(actual io.Reader) error {\n" + " return smithytesting.CompareReaderEmpty(actual)\n" + "}"); } else { String compareFunc = ""; switch (bodyMediaType.toLowerCase()) { case "application/json": compareFunc = String.format( "return smithytesting.CompareJSONReaderBytes(actual, []byte(`%s`))", body); break; case "application/xml": compareFunc = String.format( "return smithytesting.CompareXMLReaderBytes(actual, []byte(`%s`))", body); break; case "application/x-www-form-urlencoded": compareFunc = String.format( "return smithytesting.CompareURLFormReaderBytes(actual, []byte(`%s`))", body); break; case "application/cbor": compareFunc = String.format( "return smithytesting.CompareCBOR(actual, `%s`)", body); break; default: compareFunc = String.format( "return smithytesting.CompareReaderBytes(actual, []byte(`%s`))", body); break; } writeStructField(writer, "BodyAssert", "func(actual io.Reader) error {\n $L \n}", compareFunc); } } } /** * Hook to optionally generate additional setup needed before the test body is created. * * @param writer writer to write generated code with. */ protected void generateTestBodySetup(GoWriter writer) { writer.write("actualReq := &http.Request{}"); if (operation.hasTrait(RequestCompressionTrait.class)) { writer.addUseImports(SmithyGoDependency.BYTES); writer.write("rawBodyBuf := &bytes.Buffer{}"); } } /** * Hook to generate the HTTP response body of the protocol test. * * @param writer writer to write generated code with. */ protected void generateTestServerHandler(GoWriter writer) { super.generateTestServerHandler(writer); } /** * Hook to generate the body of the test that will be invoked for all test cases of this operation. Should not * do any assertions. * * @param writer writer to write generated code with. */ @Override protected void generateTestInvokeClientOperation(GoWriter writer, String clientName) { Symbol stackSymbol = SymbolUtils.createPointableSymbolBuilder("Stack", SmithyGoDependency.SMITHY_MIDDLEWARE).build(); writer.addUseImports(SmithyGoDependency.CONTEXT); writer.openBlock("result, err := $L.$T(context.Background(), c.Params, func(options *Options) {", "})", clientName, opSymbol, () -> { writer.openBlock("options.APIOptions = append(options.APIOptions, func(stack $P) error {", "})", stackSymbol, () -> { writer.write("return $T(stack, actualReq)", SymbolUtils.createValueSymbolBuilder("AddCaptureRequestMiddleware", SmithyGoDependency.SMITHY_PRIVATE_PROTOCOL).build()); }); if (operation.hasTrait(RequestCompressionTrait.class)) { writer.write(goTemplate(""" options.APIOptions = append(options.APIOptions, func(stack $stack:P) error { return $captureRequest:T(stack, rawBodyBuf) }) """, MapUtils.of( "stack", SmithyGoTypes.Middleware.Stack, "captureRequest", AddCaptureUncompressedRequest ))); } }); if (operation.hasTrait(RequestCompressionTrait.class)) { writer.write(goTemplate(""" disable := $client:L.Options().DisableRequestCompression min := $client:L.Options().RequestMinCompressSizeBytes """, MapUtils.of( "client", clientName ))); } } /** * Hook to generate the assertions for the operation's test cases. Will be in the same scope as the test body. * * @param writer writer to write generated code with. */ @Override protected void generateTestAssertions(GoWriter writer) { writeAssertNil(writer, "err"); writeAssertNotNil(writer, "result"); writeAssertScalarEqual(writer, "c.ExpectMethod", "actualReq.Method", "method"); writeAssertScalarEqual(writer, "c.ExpectURIPath", "actualReq.URL.RawPath", "path"); writeQueryItemBreakout(writer, "actualReq.URL.RawQuery", "queryItems"); writeAssertHasQuery(writer, "c.ExpectQuery", "queryItems"); writeAssertRequireQuery(writer, "c.RequireQuery", "queryItems"); writeAssertForbidQuery(writer, "c.ForbidQuery", "queryItems"); writeAssertHasHeader(writer, "c.ExpectHeader", "actualReq.Header"); writeAssertRequireHeader(writer, "c.RequireHeader", "actualReq.Header"); writeAssertForbidHeader(writer, "c.ForbidHeader", "actualReq.Header"); writer.openBlock("if c.BodyAssert != nil {", "}", () -> { writer.openBlock("if err := c.BodyAssert(actualReq.Body); err != nil {", "}", () -> { writer.write("t.Errorf(\"expect body equal, got %v\", err)"); }); }); if (operation.hasTrait(RequestCompressionTrait.class)) { String algorithm = operation.expectTrait(RequestCompressionTrait.class).getEncodings() .stream().filter(it -> ALLOWED_ALGORITHMS.contains(it)).findFirst().get(); writer.write(goTemplate(""" if err := smithytesting.CompareCompressedBytes(rawBodyBuf, actualReq.Body, disable, min, $algorithm:S); err != nil { t.Errorf("unzipped request body not match: %q", err) } """, MapUtils.of( "algorithm", algorithm ))); } } public static class Builder extends HttpProtocolUnitTestGenerator.Builder { @Override public HttpProtocolUnitTestRequestGenerator build() { return new HttpProtocolUnitTestRequestGenerator(this); } } @Override protected void generateTestServer( GoWriter writer, String name, Consumer handler ) { // We aren't using a test server, but we do need a URL to set. writer.write("serverURL := \"http://localhost:8888/\""); writer.pushState(); writer.putContext("parse", SymbolUtils.createValueSymbolBuilder("Parse", SmithyGoDependency.NET_URL) .build()); writer.write(""" if c.Host != nil { u, err := $parse:T(serverURL) if err != nil { t.Fatalf("expect no error, got %v", err) } u.Path = c.Host.Path u.RawPath = c.Host.RawPath u.RawQuery = c.Host.RawQuery serverURL = u.String() }"""); writer.popState(); } } HttpProtocolUnitTestResponseErrorGenerator.java000066400000000000000000000217241463735525100454350ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. * * */ package software.amazon.smithy.go.codegen.integration; import java.util.List; import java.util.Set; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; 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.StructureShape; import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase; /** * Generates HTTP protocol unit tests for HTTP response API error test cases. */ public class HttpProtocolUnitTestResponseErrorGenerator extends HttpProtocolUnitTestResponseGenerator { protected final StructureShape errorShape; protected final Symbol errorSymbol; /** * Initializes the protocol test generator. * * @param builder the builder initializing the generator. */ protected HttpProtocolUnitTestResponseErrorGenerator(Builder builder) { super(builder); this.errorShape = builder.error; this.errorSymbol = symbolProvider.toSymbol(errorShape); } /** * Provides the unit test function's format string. * * @return returns format string paired with unitTestFuncNameArgs */ @Override protected String unitTestFuncNameFormat() { return "TestClient_$L_$L_$LDeserialize"; } /** * Provides the unit test function name's format string arguments. * * @return returns a list of arguments used to format the unitTestFuncNameFormat returned format string. */ @Override protected Object[] unitTestFuncNameArgs() { return new Object[]{opSymbol.getName(), errorSymbol.getName(), protocolName}; } /** * Hook to generate the parameter declarations as struct parameters into the test case's struct definition. * Must generate all test case parameters before returning. * * @param writer writer to write generated code with. */ @Override protected void generateTestCaseParams(GoWriter writer) { writer.write("StatusCode int"); // TODO authScheme writer.addUseImports(SmithyGoDependency.NET_HTTP); writer.write("Header http.Header"); // TODO why do these exist? // writer.write("RequireHeaders []string"); // writer.write("ForbidHeaders []string"); writer.write("BodyMediaType string"); writer.write("Body []byte"); // TODO vendorParams for requestID writer.write("ExpectError $P", errorSymbol); } /** * Hook to generate all the test case parameters as struct member values for a single test case. * Must generate all test case parameter values before returning. * * @param writer writer to write generated code with. * @param testCase definition of a single test case. */ @Override protected void generateTestCaseValues(GoWriter writer, HttpResponseTestCase testCase) { writeStructField(writer, "StatusCode", testCase.getCode()); writeHeaderStructField(writer, "Header", testCase.getHeaders()); testCase.getBodyMediaType().ifPresent(mediaType -> { writeStructField(writer, "BodyMediaType", "$S", mediaType); }); testCase.getBody().ifPresent(body -> { var mediaType = testCase.getBodyMediaType().orElse(""); if (mediaType.equalsIgnoreCase("application/cbor")) { writeStructField(writer, "Body", """ func() []byte { p, err := $T.DecodeString(`$L`) if err != nil { panic(err) } return p }()""", SmithyGoDependency.BASE64.func("StdEncoding"), body); } else { writeStructField(writer, "Body", "[]byte(`$L`)", body); } }); writeStructField(writer, "ExpectError", errorShape, testCase.getParams()); } /** * Hook to generate the body of the test that will be invoked for all test cases of this operation. Should not * do any assertions. * * @param writer writer to write generated code with. */ @Override protected void generateTestAssertions(GoWriter writer) { writeAssertNotNil(writer, "err"); writeAssertNil(writer, "result"); // Operation Metadata writer.openBlock("var opErr interface{", "}", () -> { writer.write("Service() string"); writer.write("Operation() string"); }); writer.addUseImports(SmithyGoDependency.ERRORS); writer.openBlock("if !errors.As(err, &opErr) {", "}", () -> { writer.write("t.Fatalf(\"expect $P operation error, got %T\", err)", errorSymbol); }); writer.openBlock("if e, a := ServiceID, opErr.Service(); e != a {", "}", () -> { writer.write("t.Errorf(\"expect %v operation service name, got %v\", e, a)"); }); writer.openBlock("if e, a := $S, opErr.Operation(); e != a {", "}", opSymbol.getName(), () -> { writer.write("t.Errorf(\"expect %v operation service name, got %v\", e, a)"); }); // Smithy API error writer.write("var actualErr $P", errorSymbol); writer.openBlock("if !errors.As(err, &actualErr) {", "}", () -> { writer.write("t.Fatalf(\"expect $P result error, got %T\", err)", errorSymbol); }); writeAssertComplexEqual(writer, "c.ExpectError", "actualErr", new String[]{"middleware.Metadata{}"}); // TODO assertion for protocol metadata } public static class Builder extends HttpProtocolUnitTestResponseGenerator.Builder { protected StructureShape error; // TODO should be a way not to define these override methods since they are all defined in the base Builder. // the return type breaks this though since this builder adds a new builder field. @Override public Builder model(Model model) { super.model(model); return this; } @Override public Builder symbolProvider(SymbolProvider symbolProvider) { super.symbolProvider(symbolProvider); return this; } @Override public Builder protocolName(String protocolName) { super.protocolName(protocolName); return this; } @Override public Builder service(ServiceShape service) { super.service(service); return this; } @Override public Builder operation(OperationShape operation) { super.operation(operation); return this; } public Builder error(StructureShape error) { this.error = error; return this; } @Override public Builder testCases(List testCases) { super.testCases(testCases); return this; } @Override public Builder addTestCases(List testCases) { super.addTestCases(testCases); return this; } @Override public Builder clientConfigValue(ConfigValue configValue) { super.clientConfigValue(configValue); return this; } @Override public Builder clientConfigValues(Set clientConfigValues) { super.clientConfigValues(clientConfigValues); return this; } @Override public Builder addClientConfigValues(Set clientConfigValues) { super.addClientConfigValues(clientConfigValues); return this; } @Override public Builder skipTest(SkipTest skipTest) { super.skipTest(skipTest); return this; } @Override public Builder skipTests(Set skipTests) { super.skipTests(skipTests); return this; } @Override public Builder addSkipTests(Set skipTests) { super.addSkipTests(skipTests); return this; } @Override public HttpProtocolUnitTestResponseErrorGenerator build() { return new HttpProtocolUnitTestResponseErrorGenerator(this); } } } HttpProtocolUnitTestResponseGenerator.java000066400000000000000000000177651463735525100444350ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. * * */ package software.amazon.smithy.go.codegen.integration; import java.util.function.Consumer; import java.util.logging.Logger; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase; /** * Generates HTTP protocol unit tests for HTTP response test cases. */ public class HttpProtocolUnitTestResponseGenerator extends HttpProtocolUnitTestGenerator { private static final Logger LOGGER = Logger.getLogger(HttpProtocolUnitTestResponseGenerator.class.getName()); /** * Initializes the protocol test generator. * * @param builder the builder initializing the generator. */ protected HttpProtocolUnitTestResponseGenerator(Builder builder) { super(builder); } /** * Provides the unit test function's format string. * * @return returns format string paired with unitTestFuncNameArgs */ @Override protected String unitTestFuncNameFormat() { return "TestClient_$L_$LDeserialize"; } /** * Provides the unit test function name's format string arguments. * * @return returns a list of arguments used to format the unitTestFuncNameFormat returned format string. */ @Override protected Object[] unitTestFuncNameArgs() { return new Object[]{opSymbol.getName(), protocolName}; } /** * Hook to generate the parameter declarations as struct parameters into the test case's struct definition. * Must generate all test case parameters before returning. * * @param writer writer to write generated code with. */ @Override protected void generateTestCaseParams(GoWriter writer) { writer.write("StatusCode int"); // TODO authScheme writer.addUseImports(SmithyGoDependency.NET_HTTP); writer.write("Header http.Header"); // TODO why do these exist? // writer.write("RequireHeaders []string"); // writer.write("ForbidHeaders []string"); writer.write("BodyMediaType string"); writer.write("Body []byte"); writer.write("ExpectResult $P", outputSymbol); } /** * Hook to generate all the test case parameters as struct member values for a single test case. * Must generate all test case parameter values before returning. * * @param writer writer to write generated code with. * @param testCase definition of a single test case. */ @Override protected void generateTestCaseValues(GoWriter writer, HttpResponseTestCase testCase) { writeStructField(writer, "StatusCode", testCase.getCode()); writeHeaderStructField(writer, "Header", testCase.getHeaders()); testCase.getBodyMediaType().ifPresent(mediaType -> { writeStructField(writer, "BodyMediaType", "$S", mediaType); }); testCase.getBody().ifPresent(body -> { var mediaType = testCase.getBodyMediaType().orElse(""); if (mediaType.equalsIgnoreCase("application/cbor")) { writeStructField(writer, "Body", """ func() []byte { p, err := $T.DecodeString(`$L`) if err != nil { panic(err) } return p }()""", SmithyGoDependency.BASE64.func("StdEncoding"), body); } else { writeStructField(writer, "Body", "[]byte(`$L`)", body); } }); writeStructField(writer, "ExpectResult", outputShape, testCase.getParams()); } @Override protected void generateTestClient(GoWriter writer, String clientName) { writer.openBlock("$L := New(Options{", "})", clientName, () -> { writer.addUseImports(SmithyGoDependency.SMITHY_HTTP_TRANSPORT); writer.openBlock("HTTPClient: smithyhttp.ClientDoFunc(func(r *http.Request) (*http.Response, error) {", "}),", () -> generateResponse(writer)); for (ConfigValue value : clientConfigValues) { writeStructField(writer, value.getName(), value.getValue()); } }); } @Override protected void generateTestServer(GoWriter writer, String name, Consumer handler) { // We aren't using a test server, but we do need a URL to set. writer.write("serverURL := \"http://localhost:8888/\""); } /** * Generates a Response object to return for testing. * * @param writer The writer to write generated code with. */ protected void generateResponse(GoWriter writer) { writer.addUseImports(SmithyGoDependency.NET_HTTP); writer.write("headers := http.Header{}"); writer.openBlock("for k, vs := range c.Header {", "}", () -> { writer.openBlock("for _, v := range vs {", "}", () -> { writer.write("headers.Add(k, v)"); }); }); writer.openBlock("if len(c.BodyMediaType) != 0 && len(headers.Values(\"Content-Type\")) == 0 {", "}", () -> { writer.write("headers.Set(\"Content-Type\", c.BodyMediaType)"); }); writer.openBlock("response := &http.Response{", "}", () -> { writer.write("StatusCode: c.StatusCode,"); writer.write("Header: headers,"); writer.write("Request: r,"); }); writer.addUseImports(SmithyGoDependency.BYTES); writer.addUseImports(SmithyGoDependency.IOUTIL); writer.openBlock("if len(c.Body) != 0 {", "} else {", () -> { writer.write("response.ContentLength = int64(len(c.Body))"); writer.write("response.Body = ioutil.NopCloser(bytes.NewReader(c.Body))"); }); writer.openBlock("", "}", () -> { // We have to set this special sentinel value for no body, or anything that relies on there being // a value set will panic. writer.write("response.Body = http.NoBody"); }); writer.write("return response, nil"); } /** * Hook to generate the body of the test that will be invoked for all test cases of this operation. Should not * do any assertions. * * @param writer writer to write generated code with. */ @Override protected void generateTestInvokeClientOperation(GoWriter writer, String clientName) { writer.addUseImports(SmithyGoDependency.CONTEXT); writer.write("var params $T", inputSymbol); writer.write("result, err := $L.$T(context.Background(), ¶ms)", clientName, opSymbol); } /** * Hook to generate the assertions for the operation's test cases. Will be in the same scope as the test body. * * @param writer writer to write generated code with. */ @Override protected void generateTestAssertions(GoWriter writer) { writeAssertNil(writer, "err"); writeAssertNotNil(writer, "result"); writer.addUseImports(SmithyGoDependency.SMITHY_MIDDLEWARE); writeAssertComplexEqual(writer, "c.ExpectResult", "result", new String[]{"middleware.Metadata{}"}); // TODO assertion for protocol metadata } public static class Builder extends HttpProtocolUnitTestGenerator.Builder { @Override public HttpProtocolUnitTestResponseGenerator build() { return new HttpProtocolUnitTestResponseGenerator(this); } } } HttpProtocolUtils.java000066400000000000000000000121121463735525100403450ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. * * */ package software.amazon.smithy.go.codegen.integration; import java.util.List; import java.util.Optional; import java.util.function.BiPredicate; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.EventStreamIndex; import software.amazon.smithy.model.knowledge.HttpBinding; import software.amazon.smithy.model.knowledge.HttpBindingIndex; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.ListUtils; /** * Provides a set of RuntimePlugins to ensure that a HTTP response is closed when it needs to be needed. This should be * used by all HTTP based protocols to ensure that the response body is closed, and any errors are checked. This * ensures that connections are not leaked by the underlying HTTP client. */ public final class HttpProtocolUtils { private HttpProtocolUtils() { } /** * Returns a set of RuntimePlugs to close the HTTP operation response. Uses the servicePredicate parameter to * filter the RuntimePlugins to protocols that are relevant. * * @param servicePredicate service filter * @return RuntimePlugins */ public static List getCloseResponseClientPlugins( BiPredicate servicePredicate ) { return ListUtils.of( // Add deserialization middleware to close the response in case of errors. RuntimeClientPlugin.builder() .servicePredicate(servicePredicate) .operationPredicate((model, service, operation) -> { var eventStreamIndex = EventStreamIndex.of(model); return eventStreamIndex.getInputInfo(operation).isEmpty() && eventStreamIndex.getOutputInfo(operation).isEmpty(); }) .registerMiddleware(MiddlewareRegistrar.builder() .resolvedFunction(SymbolUtils.createValueSymbolBuilder( "AddErrorCloseResponseBodyMiddleware", SmithyGoDependency.SMITHY_HTTP_TRANSPORT) .build()) .build() ) .build(), // Add deserialization middleware to close the response for non-output-streaming operations. RuntimeClientPlugin.builder() .servicePredicate(servicePredicate) .operationPredicate((model, service, operation) -> { // Don't auto close response body when response is streaming. HttpBindingIndex httpBindingIndex = HttpBindingIndex.of(model); Optional payloadBinding = httpBindingIndex.getResponseBindings(operation, HttpBinding.Location.PAYLOAD).stream().findFirst(); var eventStreamIndex = EventStreamIndex.of(model); if (eventStreamIndex.getInputInfo(operation).isPresent() || eventStreamIndex.getOutputInfo(operation).isPresent()) { return false; } if (payloadBinding.isPresent()) { MemberShape memberShape = payloadBinding.get().getMember(); Shape payloadShape = model.expectShape(memberShape.getTarget()); return !payloadShape.hasTrait(StreamingTrait.class); } return true; }) .registerMiddleware(MiddlewareRegistrar.builder() .resolvedFunction(SymbolUtils.createValueSymbolBuilder( "AddCloseResponseBodyMiddleware", SmithyGoDependency.SMITHY_HTTP_TRANSPORT) .build()) .build() ) .build() ); } } HttpRpcProtocolGenerator.java000066400000000000000000000512221463735525100416450ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.ApplicationProtocol; import software.amazon.smithy.go.codegen.CodegenUtils; import software.amazon.smithy.go.codegen.GoEventStreamIndex; import software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.EventStreamIndex; import software.amazon.smithy.model.knowledge.EventStreamInfo; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.UnionShape; public abstract class HttpRpcProtocolGenerator implements ProtocolGenerator { private final Set serializingDocumentShapes = new TreeSet<>(); private final Set deserializingDocumentShapes = new TreeSet<>(); private final Set deserializingErrorShapes = new TreeSet<>(); /** * Creates an Http RPC protocol generator. */ public HttpRpcProtocolGenerator() { } @Override public ApplicationProtocol getApplicationProtocol() { return ApplicationProtocol.createDefaultHttpApplicationProtocol(); } /** * Gets the content-type for a request body. * * @return Returns the default content-type. */ protected abstract String getDocumentContentType(); @Override public void generateSharedSerializerComponents(GenerationContext context) { serializingDocumentShapes.addAll(ProtocolUtils.resolveRequiredDocumentShapeSerde( context.getModel(), serializingDocumentShapes)); generateDocumentBodyShapeSerializers(context, serializingDocumentShapes); } /** * Generates serialization functions for shapes in the passed set. These functions * should return a value that can then be serialized by the implementation of * {@code serializeInputDocument}. * * @param context The generation context. * @param shapes The shapes to generate serialization for. */ protected abstract void generateDocumentBodyShapeSerializers(GenerationContext context, Set shapes); @Override public void generateRequestSerializers(GenerationContext context) { Model model = context.getModel(); TopDownIndex topDownIndex = TopDownIndex.of(model); for (OperationShape operation : topDownIndex.getContainedOperations(context.getService())) { generateOperationSerializer(context, operation); } GoEventStreamIndex goEventStreamIndex = GoEventStreamIndex.of(context.getModel()); goEventStreamIndex.getInputEventStreams(context.getService()).ifPresent(shapeIdSetMap -> shapeIdSetMap.forEach((shapeId, eventStreamInfos) -> { generateEventStreamSerializers(context, context.getModel().expectShape(shapeId, UnionShape.class), eventStreamInfos); })); } /** * Generate the event stream serializers for the given event stream target and associated operations. * * @param context the generation context * @param eventUnion the event stream union * @param eventStreamInfos the event stream infos */ protected abstract void generateEventStreamSerializers( GenerationContext context, UnionShape eventUnion, Set eventStreamInfos ); /** * Generate the event stream deserializers for the given event stream target and asscioated operations. * * @param context the generation context * @param eventUnion the event stream union * @param eventStreamInfos the event stream infos */ protected abstract void generateEventStreamDeserializers( GenerationContext context, UnionShape eventUnion, Set eventStreamInfos ); private void generateOperationSerializer(GenerationContext context, OperationShape operation) { SymbolProvider symbolProvider = context.getSymbolProvider(); Model model = context.getModel(); ServiceShape service = context.getService(); Shape inputShape = ProtocolUtils.expectInput(model, operation); Symbol inputSymbol = symbolProvider.toSymbol(inputShape); ApplicationProtocol applicationProtocol = getApplicationProtocol(); Symbol requestType = applicationProtocol.getRequestType(); GoWriter writer = context.getWriter().get(); writer.pushState(); GoStackStepMiddlewareGenerator middleware = GoStackStepMiddlewareGenerator.createSerializeStepMiddleware( ProtocolGenerator.getSerializeMiddlewareName(operation.getId(), service, getProtocolName()), ProtocolUtils.OPERATION_SERIALIZER_MIDDLEWARE_ID); middleware.writeMiddleware(context.getWriter().get(), (generator, w) -> { writer.addUseImports(SmithyGoDependency.SMITHY); writer.addUseImports(SmithyGoDependency.FMT); writer.addUseImports(SmithyGoDependency.SMITHY_HTTP_BINDING); // TODO: refactor the http binding encoder to be split up into its component parts // This would allow most of this shared code to be split off into its own function // to reduce duplication, and potentially allowing it to be a static function. // For example, a HeaderBag type could handle all the headers. // Cast the input request to the transport request type and check for errors. writer.write("request, ok := in.Request.($P)", requestType); writer.openBlock("if !ok {", "}", () -> { writer.write("return out, metadata, " + "&smithy.SerializationError{Err: fmt.Errorf(\"unknown transport type %T\", in.Request)}" ); }).write(""); // Cast the input parameters to the operation request type and check for errors. writer.write("input, ok := in.Parameters.($P)", inputSymbol); writer.write("_ = input"); writer.openBlock("if !ok {", "}", () -> { writer.write("return out, metadata, " + "&smithy.SerializationError{Err: fmt.Errorf(\"unknown input parameters type %T\"," + " in.Parameters)}"); }).write(""); writer.putContext("opPath", getOperationPath(context, operation)); writer.putContext("pathJoin", SymbolUtils.createValueSymbolBuilder("Join", SmithyGoDependency.PATH).build()); writer.write(""" operationPath := $opPath:S if len(request.Request.URL.Path) == 0 { request.Request.URL.Path = operationPath } else { request.Request.URL.Path = $pathJoin:T(request.Request.URL.Path, operationPath) if request.Request.URL.Path != "/" && operationPath[len(operationPath)-1] == '/' { request.Request.URL.Path += "/" } }"""); writer.write("request.Request.Method = \"POST\""); writer.write("httpBindingEncoder, err := httpbinding.NewEncoder(request.URL.Path, " + "request.URL.RawQuery, request.Header)"); writer.openBlock("if err != nil {", "}", () -> { writer.write("return out, metadata, &smithy.SerializationError{Err: err}"); }); Optional optionalEventStreamInfo = EventStreamIndex.of(model).getInputInfo(operation); // Skip and Handle Input Event Stream Setup Separately if (optionalEventStreamInfo.isEmpty()) { writeRequestHeaders(context, operation, writer); writer.write(""); // delegate the setup and usage of the document serializer function for the protocol serializeInputDocument(context, operation); // Skipping calling serializer method for the input shape is responsibility of the // serializeInputDocument implementation. if (!CodegenUtils.isStubSynthetic(ProtocolUtils.expectInput(context.getModel(), operation))) { serializingDocumentShapes.add(ProtocolUtils.expectInput(model, operation)); } } else { writeDefaultHeaders(context, operation, writer); writeOperationSerializerMiddlewareEventStreamSetup(context, optionalEventStreamInfo.get()); } writer.write(""); writer.openBlock("if request.Request, err = httpBindingEncoder.Encode(request.Request); err != nil {", "}", () -> { writer.write("return out, metadata, &smithy.SerializationError{Err: err}"); }); // Ensure the request value is updated if modified for a document. writer.write("in.Request = request"); writer.write(""); writer.write("return next.$L(ctx, in)", generator.getHandleMethodName()); }); writer.popState(); } protected abstract void writeOperationSerializerMiddlewareEventStreamSetup( GenerationContext context, EventStreamInfo eventStreamInfo ); private void writeRequestHeaders(GenerationContext context, OperationShape operation, GoWriter writer) { writer.write("httpBindingEncoder.SetHeader(\"Content-Type\").String($S)", getDocumentContentType()); writeDefaultHeaders(context, operation, writer); } /** * Writes any additional HTTP headers required by the protocol implementation. * *

Four parameters will be available in scope: *

    *
  • {@code input: }: the type generated for the operation's input.
  • *
  • {@code request: smithyhttp.HTTPRequest}: the HTTP request that will be sent.
  • *
  • {@code httpBindingEncoder: httpbinding.Encoder}: the HTTP encoder to use to set the headers.
  • *
  • {@code ctx: context.Context}: a type containing context and tools for type serde.
  • *
* * @param context The generation context. * @param operation The operation being generated. * @param writer The writer to use. */ protected void writeDefaultHeaders(GenerationContext context, OperationShape operation, GoWriter writer) { } /** * Provides the request path for the operation. * * @param context The generation context. * @param operation The operation being generated. * @return The path to send HTTP requests to. */ protected abstract String getOperationPath(GenerationContext context, OperationShape operation); /** * Generate the document serializer logic for the serializer middleware body. * *

Three parameters will be available in scope: *

    *
  • {@code input: }: the type generated for the operation's input.
  • *
  • {@code request: smithyhttp.HTTPRequest}: the HTTP request that will be sent.
  • *
  • {@code ctx: context.Context}: a type containing context and tools for type serde.
  • *
* * @param context The generation context. * @param operation The operation to serialize for. */ protected abstract void serializeInputDocument(GenerationContext context, OperationShape operation); @Override public void generateSharedDeserializerComponents(GenerationContext context) { deserializingErrorShapes.forEach(error -> generateErrorDeserializer(context, error)); deserializingDocumentShapes.addAll(ProtocolUtils.resolveRequiredDocumentShapeSerde( context.getModel(), deserializingDocumentShapes)); generateDocumentBodyShapeDeserializers(context, deserializingDocumentShapes); } /** * Generated deserialization functions for shapes in the passed set. These functions * should return a value that can then be serialized by the implementation of * {@code deserializeOutputDocument}. * * @param context The generation context. * @param shapes The shapes to generate deserialization for. */ protected abstract void generateDocumentBodyShapeDeserializers(GenerationContext context, Set shapes); @Override public void generateResponseDeserializers(GenerationContext context) { TopDownIndex topDownIndex = context.getModel().getKnowledge(TopDownIndex.class); Set containedOperations = new TreeSet<>( topDownIndex.getContainedOperations(context.getService())); for (OperationShape operation : containedOperations) { generateOperationDeserializer(context, operation); } GoEventStreamIndex goEventStreamIndex = GoEventStreamIndex.of(context.getModel()); goEventStreamIndex.getOutputEventStreams(context.getService()).ifPresent(shapeIdSetMap -> shapeIdSetMap.forEach((shapeId, eventStreamInfos) -> { generateEventStreamDeserializers(context, context.getModel().expectShape(shapeId, UnionShape.class), eventStreamInfos); })); } private void generateOperationDeserializer(GenerationContext context, OperationShape operation) { SymbolProvider symbolProvider = context.getSymbolProvider(); Model model = context.getModel(); GoWriter writer = context.getWriter().get(); ServiceShape service = context.getService(); StructureShape outputShape = ProtocolUtils.expectOutput(context.getModel(), operation); Symbol outputSymbol = symbolProvider.toSymbol(outputShape); ApplicationProtocol applicationProtocol = getApplicationProtocol(); Symbol responseType = applicationProtocol.getResponseType(); String errorFunctionName = ProtocolGenerator.getOperationErrorDeserFunctionName( operation, context.getService(), context.getProtocolName()); GoStackStepMiddlewareGenerator middleware = GoStackStepMiddlewareGenerator.createDeserializeStepMiddleware( ProtocolGenerator.getDeserializeMiddlewareName(operation.getId(), service, getProtocolName()), ProtocolUtils.OPERATION_DESERIALIZER_MIDDLEWARE_ID); middleware.writeMiddleware(writer, (generator, w) -> { writer.addUseImports(SmithyGoDependency.FMT); writer.addUseImports(SmithyGoDependency.SMITHY); writer.write("out, metadata, err = next.$L(ctx, in)", generator.getHandleMethodName()); writer.write("if err != nil { return out, metadata, err }"); writer.write(""); writer.write("response, ok := out.RawResponse.($P)", responseType); writer.openBlock("if !ok {", "}", () -> { writer.write(String.format("return out, metadata, &smithy.DeserializationError{Err: %s}", "fmt.Errorf(\"unknown transport type %T\", out.RawResponse)")); }); writer.write(""); writer.openBlock("if response.StatusCode < 200 || response.StatusCode >= 300 {", "}", () -> { writer.write("return out, metadata, $L(response, &metadata)", errorFunctionName); }); writer.write("output := &$T{}", outputSymbol); writer.write("out.Result = output"); writer.write(""); Optional streamInfoOptional = EventStreamIndex.of(model).getOutputInfo(operation); // Discard without deserializing the response if the input shape is a stubbed synthetic clone // without an archetype. if (CodegenUtils.isStubSynthetic(ProtocolUtils.expectOutput(model, operation)) && streamInfoOptional.isEmpty()) { writer.addUseImports(SmithyGoDependency.IOUTIL); writer.openBlock("if _, err = io.Copy(ioutil.Discard, response.Body); err != nil {", "}", () -> { writer.openBlock("return out, metadata, &smithy.DeserializationError{", "}", () -> { writer.write("Err: fmt.Errorf(\"failed to discard response body, %w\", err),"); }); }); } else if (streamInfoOptional.isEmpty()) { deserializeOutputDocument(context, operation); deserializingDocumentShapes.add(ProtocolUtils.expectOutput(model, operation)); } writer.write(""); writer.write("return out, metadata, err"); }); writer.write(""); Set errorShapes = generateErrorShapes(context, operation, responseType); deserializingErrorShapes.addAll(errorShapes); deserializingDocumentShapes.addAll(errorShapes); } protected Set generateErrorShapes( GenerationContext context, OperationShape operation, Symbol responseType) { return HttpProtocolGeneratorUtils.generateErrorDispatcher( context, operation, responseType, this::writeErrorMessageCodeDeserializer, this::getOperationErrors); } /** * Generate the document deserializer logic for the deserializer middleware body. * *

Three parameters will be available in scope: *

    *
  • {@code output: }: the type generated for the operation's output.
  • *
  • {@code response: smithyhttp.HTTPRequest}: the HTTP response received.
  • *
  • {@code ctx: context.Context}: a type containing context and tools for type serde.
  • *
* * @param context The generation context * @param operation The operation to deserialize for. */ protected abstract void deserializeOutputDocument(GenerationContext context, OperationShape operation); private void generateErrorDeserializer(GenerationContext context, StructureShape shape) { GoWriter writer = context.getWriter().get(); String functionName = ProtocolGenerator.getErrorDeserFunctionName( shape, context.getService(), context.getProtocolName()); Symbol responseType = getApplicationProtocol().getResponseType(); writer.addUseImports(SmithyGoDependency.BYTES); writer.openBlock("func $L(response $P, errorBody *bytes.Reader) error {", "}", functionName, responseType, () -> deserializeError(context, shape)); writer.write(""); } /** * Writes a function body that deserializes the given error. * *

Two parameters will be available in scope: *

    *
  • {@code response: smithyhttp.HTTPResponse}: the HTTP response received.
  • *
  • {@code errorBody: bytes.BytesReader}: the HTTP response body.
  • *
* * @param context The generation context. * @param shape The error shape. */ protected abstract void deserializeError(GenerationContext context, StructureShape shape); /** * Writes a code snippet that gets the error code and error message. * *

Four parameters will be available in scope: *

    *
  • {@code response: smithyhttp.HTTPResponse}: the HTTP response received.
  • *
  • {@code errorBody: bytes.BytesReader}: the HTTP response body.
  • *
  • {@code errorMessage: string}: the error message initialized to a default value.
  • *
  • {@code errorCode: string}: the error code initialized to a default value.
  • *
* * @param context the generation context. */ protected abstract void writeErrorMessageCodeDeserializer(GenerationContext context); } IdempotencyTokenMiddlewareGenerator.java000066400000000000000000000264451463735525100440270ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.MiddlewareIdentifier; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.OperationIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; 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.traits.IdempotencyTokenTrait; import software.amazon.smithy.utils.ListUtils; public class IdempotencyTokenMiddlewareGenerator implements GoIntegration { public static final String IDEMPOTENCY_CONFIG_NAME = "IdempotencyTokenProvider"; public static final MiddlewareIdentifier OPERATION_IDEMPOTENCY_TOKEN_MIDDLEWARE_ID = MiddlewareIdentifier .string("OperationIdempotencyTokenAutoFill"); List runtimeClientPlugins = new ArrayList<>(); private void execute( Model model, GoWriter writer, SymbolProvider symbolProvider, OperationShape operation, MemberShape idempotencyTokenMemberShape ) { GoStackStepMiddlewareGenerator middlewareGenerator = GoStackStepMiddlewareGenerator.createInitializeStepMiddleware( getIdempotencyTokenMiddlewareName(operation), OPERATION_IDEMPOTENCY_TOKEN_MIDDLEWARE_ID ); Shape inputShape = model.expectShape(operation.getInput().get()); Symbol inputSymbol = symbolProvider.toSymbol(inputShape); String memberName = symbolProvider.toMemberName(idempotencyTokenMemberShape); middlewareGenerator.writeMiddleware(writer, (generator, middlewareWriter) -> { // if token provider is nil, skip this middleware middlewareWriter.openBlock("if m.tokenProvider == nil {", "}", () -> { middlewareWriter.write("return next.$L(ctx, in)", middlewareGenerator.getHandleMethodName()); }); writer.write(""); middlewareWriter.write("input, ok := in.Parameters.($P)", inputSymbol); middlewareWriter.write("if !ok { return out, metadata, " + "fmt.Errorf(\"expected middleware input to be of type $P \")}", inputSymbol); middlewareWriter.addUseImports(SmithyGoDependency.FMT); writer.write(""); middlewareWriter.openBlock("if input.$L == nil {", "}", memberName, () -> { middlewareWriter.write("t, err := m.tokenProvider.GetIdempotencyToken()"); middlewareWriter.write(" if err != nil { return out, metadata, err }"); middlewareWriter.write("input.$L = &t", memberName); }); middlewareWriter.write("return next.$L(ctx, in)", middlewareGenerator.getHandleMethodName()); }, ((generator, memberWriter) -> { memberWriter.write("tokenProvider IdempotencyTokenProvider"); })); } @Override public void processFinalizedModel(GoSettings settings, Model model) { ServiceShape serviceShape = settings.getService(model); Map map = getOperationsWithIdempotencyToken(model, serviceShape); if (map.isEmpty()) { return; } runtimeClientPlugins.add( RuntimeClientPlugin.builder() .configFields(ListUtils.of(ConfigField.builder() .name(IDEMPOTENCY_CONFIG_NAME) .type(SymbolUtils.createValueSymbolBuilder("IdempotencyTokenProvider").build()) .documentation("Provides idempotency tokens values " + "that will be automatically populated into idempotent API operations.") .build())) .build() ); for (Map.Entry entry : map.entrySet()) { ShapeId operationShapeId = entry.getKey(); OperationShape operation = model.expectShape(operationShapeId, OperationShape.class); String getMiddlewareHelperName = getIdempotencyTokenMiddlewareHelperName(operation); RuntimeClientPlugin runtimeClientPlugin = RuntimeClientPlugin.builder() .operationPredicate((predicateModel, predicateService, predicateOperation) -> { return operation.equals(predicateOperation); }) .registerMiddleware(MiddlewareRegistrar.builder() .resolvedFunction(SymbolUtils.createValueSymbolBuilder(getMiddlewareHelperName).build()) .useClientOptions() .build()) .build(); runtimeClientPlugins.add(runtimeClientPlugin); } } /** * Gets the sort order of the customization from -128 to 127, with lowest * executed first. * * @return Returns the sort order, defaults to 10. */ @Override public byte getOrder() { return 10; } @Override public void writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator delegator ) { ServiceShape serviceShape = settings.getService(model); Map map = getOperationsWithIdempotencyToken(model, serviceShape); if (map.size() == 0) { return; } delegator.useShapeWriter(serviceShape, (writer) -> { writer.write("// IdempotencyTokenProvider interface for providing idempotency token"); writer.openBlock("type IdempotencyTokenProvider interface {", "}", () -> { writer.write("GetIdempotencyToken() (string, error)"); }); writer.write(""); }); for (Map.Entry entry : map.entrySet()) { ShapeId operationShapeId = entry.getKey(); OperationShape operation = model.expectShape(operationShapeId, OperationShape.class); delegator.useShapeWriter(operation, (writer) -> { // Generate idempotency token middleware MemberShape memberShape = map.get(operationShapeId); execute(model, writer, symbolProvider, operation, memberShape); // Generate idempotency token middleware registrar function writer.addUseImports(SmithyGoDependency.SMITHY_MIDDLEWARE); String middlewareHelperName = getIdempotencyTokenMiddlewareHelperName(operation); writer.openBlock("func $L(stack *middleware.Stack, cfg Options) error {", "}", middlewareHelperName, () -> { writer.write("return stack.Initialize.Add(&$L{tokenProvider: cfg.$L}, middleware.Before)", getIdempotencyTokenMiddlewareName(operation), IDEMPOTENCY_CONFIG_NAME); }); }); } } @Override public List getClientPlugins() { return runtimeClientPlugins; } /** * Get Idempotency Token Middleware name. * * @param operationShape Operation shape for which middleware is defined. * @return name of the idempotency token middleware. */ private String getIdempotencyTokenMiddlewareName(OperationShape operationShape) { return String.format("idempotencyToken_initializeOp%s", operationShape.getId().getName()); } /** * Get Idempotency Token Middleware Helper name. * * @param operationShape Operation shape for which middleware is defined. * @return name of the idempotency token middleware. */ private String getIdempotencyTokenMiddlewareHelperName(OperationShape operationShape) { return String.format("addIdempotencyToken_op%sMiddleware", operationShape.getId().getName()); } /** * Gets a map with key as OperationId and Member shape as value for member shapes of an operation * decorated with the Idempotency token trait. * * @param model Model used for generation. * @param service Service for which idempotency token map is retrieved. * @return map of operation shapeId as key, member shape as value. */ public static Map getOperationsWithIdempotencyToken(Model model, ServiceShape service) { Map map = new TreeMap<>(); TopDownIndex.of(model).getContainedOperations(service).stream().forEach((operationShape) -> { MemberShape memberShape = getMemberWithIdempotencyToken(model, operationShape); if (memberShape != null) { map.put(operationShape.getId(), memberShape); } }); return map; } /** * Returns if there are any operations within the service that use idempotency token auto fill trait. * * @param model Model used for generation. * @param service Service for which idempotency token map is retrieved. * @return if operations use idempotency token auto fill trait. */ public static boolean hasOperationsWithIdempotencyToken(Model model, ServiceShape service) { return !getOperationsWithIdempotencyToken(model, service).isEmpty(); } /** * Returns member shape which gets members decorated with Idempotency Token trait. * * @param model Model used for generation. * @param operation Operation shape consisting of member decorated with idempotency token trait. * @return member shape decorated with Idempotency token trait. */ private static MemberShape getMemberWithIdempotencyToken(Model model, OperationShape operation) { OperationIndex operationIndex = model.getKnowledge(OperationIndex.class); Shape inputShape = operationIndex.getInput(operation).get(); for (MemberShape member : inputShape.members()) { if (member.hasTrait(IdempotencyTokenTrait.class)) { return member; } } return null; } } MiddlewareRegistrar.java000066400000000000000000000144451463735525100406360ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.ArrayList; import java.util.Collection; import java.util.Objects; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.ToSmithyBuilder; public class MiddlewareRegistrar implements ToSmithyBuilder { private final Symbol resolvedFunction; private final Collection functionArguments; private final String inlineRegisterMiddlewareStatement; private final Symbol inlineRegisterMiddlewarePosition; public MiddlewareRegistrar(Builder builder) { this.resolvedFunction = builder.resolvedFunction; this.functionArguments = builder.functionArguments; this.inlineRegisterMiddlewareStatement = builder.inlineRegisterMiddlewareStatement; this.inlineRegisterMiddlewarePosition = builder.inlineRegisterMiddlewarePosition; } /** * @return symbol that resolves to a function. */ public Symbol getResolvedFunction() { return resolvedFunction; } /** * @return collection of symbols denoting the arguments of the resolved function. */ public Collection getFunctionArguments() { return functionArguments; } /** * @return string denoting inline middleware registration in the stack */ public String getInlineRegisterMiddlewareStatement() { return inlineRegisterMiddlewareStatement; } /** * @return symbol used to define the middleware position in the stack */ public Symbol getInlineRegisterMiddlewarePosition() { return inlineRegisterMiddlewarePosition; } @Override public SmithyBuilder toBuilder() { return builder().functionArguments(functionArguments).resolvedFunction(resolvedFunction); } public static MiddlewareRegistrar.Builder builder() { return new MiddlewareRegistrar.Builder(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MiddlewareRegistrar that = (MiddlewareRegistrar) o; return Objects.equals(getResolvedFunction(), that.getResolvedFunction()) && Objects.equals(getFunctionArguments(), that.getFunctionArguments()); } @Override public int hashCode() { return Objects.hash(getResolvedFunction(), getFunctionArguments()); } /** * Builds a MiddlewareRegistrar. */ public static class Builder implements SmithyBuilder { private Symbol resolvedFunction; private Collection functionArguments; private String inlineRegisterMiddlewareStatement; private Symbol inlineRegisterMiddlewarePosition; @Override public MiddlewareRegistrar build() { return new MiddlewareRegistrar(this); } /** * Set the name of the MiddlewareRegistrar function. * * @param resolvedFunction a symbol that resolves to the function . * @return Returns the builder. */ public Builder resolvedFunction(Symbol resolvedFunction) { this.resolvedFunction = resolvedFunction; return this; } /** * Sets the function Arguments for the MiddlewareRegistrar function. * * @param functionArguments A collection of symbols representing the arguments * to the middleware register function. * @return Returns the builder. */ public Builder functionArguments(Collection functionArguments) { this.functionArguments = new ArrayList<>(functionArguments); return this; } /** * Sets symbol that resolves to options as an argument for the resolved function. * * @return Returns the builder. */ public Builder useClientOptions() { Collection args = new ArrayList<>(); args.add(SymbolUtils.createValueSymbolBuilder("options").build()); this.functionArguments = args; return this; } /** * Adds a middleware to the middleware stack at relative position of After. * @param stackStep Stack step. * @return Returns the Builder. */ public Builder registerAfter(MiddlewareStackStep stackStep) { this.inlineRegisterMiddlewareStatement = String.format("%s.Add(", stackStep); this.inlineRegisterMiddlewarePosition = getMiddlewareAfterPositionSymbol(); return this; } /** * Adds the middleware to the middleware stack at relative position of Before. * @param stackStep Stack step at which the middleware is to be register. * @return Returns the Builder. */ public Builder registerBefore(MiddlewareStackStep stackStep) { this.inlineRegisterMiddlewareStatement = String.format("%s.Add(", stackStep); this.inlineRegisterMiddlewarePosition = getMiddlewareBeforePositionSymbol(); return this; } private Symbol getMiddlewareAfterPositionSymbol() { return SymbolUtils.createValueSymbolBuilder("After", SmithyGoDependency.SMITHY_MIDDLEWARE).build(); } private Symbol getMiddlewareBeforePositionSymbol() { return SymbolUtils.createValueSymbolBuilder("Before", SmithyGoDependency.SMITHY_MIDDLEWARE).build(); } } } MiddlewareStackSnapshotTests.java000066400000000000000000000160731463735525100425030ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.utils.MapUtils; public class MiddlewareStackSnapshotTests implements GoIntegration { @Override public void writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator goDelegator ) { goDelegator.useFileWriter("snapshot_test.go", settings.getModuleName(), writer -> { writer.addBuildTag("snapshot"); writer.write(commonTestSource()); writer.write(snapshotTests(model, settings.getService(model), symbolProvider)); writer.write(snapshotUpdaters(model, settings.getService(model), symbolProvider)); }); } private GoWriter.Writable commonTestSource() { return goTemplate(""" $os:D $fs:D $io:D $errors:D $fmt:D $middleware:D const ssprefix = "snapshot" type snapshotOK struct{} func (snapshotOK) Error() string { return "error: success" } func createp(path string) (*os.File, error) { if err := os.Mkdir(ssprefix, 0700); err != nil && !errors.Is(err, fs.ErrExist) { return nil, err } return os.Create(path) } func sspath(op string) string { return fmt.Sprintf("%s/api_op_%s.go.snap", ssprefix, op) } func updateSnapshot(stack *middleware.Stack, operation string) error { f, err := createp(sspath(operation)) if err != nil { return err } defer f.Close() if _, err := f.Write([]byte(stack.String())); err != nil { return err } return snapshotOK{} } func testSnapshot(stack *middleware.Stack, operation string) error { f, err := os.Open(sspath(operation)) if errors.Is(err, fs.ErrNotExist) { return snapshotOK{} } if err != nil { return err } defer f.Close() expected, err := io.ReadAll(f) if err != nil { return err } if actual := stack.String(); actual != string(expected) { return fmt.Errorf("%s != %s", expected, actual) } return snapshotOK{} } """, MapUtils.of( "errors", SmithyGoDependency.ERRORS, "fmt", SmithyGoDependency.FMT, "fs", SmithyGoDependency.FS, "io", SmithyGoDependency.IO, "middleware", SmithyGoDependency.SMITHY_MIDDLEWARE, "os", SmithyGoDependency.OS )); } private GoWriter.Writable snapshotUpdaters(Model model, ServiceShape service, SymbolProvider symbolProvider) { return GoWriter.ChainWritable.of( TopDownIndex.of(model).getContainedOperations(service).stream() .map(it -> testUpdateSnapshot(it, symbolProvider)) .toList() ).compose(); } private GoWriter.Writable snapshotTests(Model model, ServiceShape service, SymbolProvider symbolProvider) { return GoWriter.ChainWritable.of( TopDownIndex.of(model).getContainedOperations(service).stream() .map(it -> testCheckSnapshot(it, symbolProvider)) .toList() ).compose(); } private GoWriter.Writable testUpdateSnapshot(OperationShape operation, SymbolProvider symbolProvider) { return goTemplate(""" func TestUpdateSnapshot_$operation:L(t $testingT:P) { svc := New(Options{}) _, err := svc.$operation:L($contextBackground:T(), nil, func(o *Options) { o.APIOptions = append(o.APIOptions, func(stack $middlewareStack:P) error { return updateSnapshot(stack, $operation:S) }) }) if _, ok := err.(snapshotOK); !ok && err != nil { t.Fatal(err) } } """, MapUtils.of( "testingT", GoStdlibTypes.Testing.T, "contextBackground", GoStdlibTypes.Context.Background, "middlewareStack", SmithyGoTypes.Middleware.Stack, "operation", symbolProvider.toSymbol(operation).getName() )); } private GoWriter.Writable testCheckSnapshot(OperationShape operation, SymbolProvider symbolProvider) { return goTemplate(""" func TestCheckSnapshot_$operation:L(t $testingT:P) { svc := New(Options{}) _, err := svc.$operation:L($contextBackground:T(), nil, func(o *Options) { o.APIOptions = append(o.APIOptions, func(stack $middlewareStack:P) error { return testSnapshot(stack, $operation:S) }) }) if _, ok := err.(snapshotOK); !ok && err != nil { t.Fatal(err) } } """, MapUtils.of( "testingT", GoStdlibTypes.Testing.T, "contextBackground", GoStdlibTypes.Context.Background, "middlewareStack", SmithyGoTypes.Middleware.Stack, "operation", symbolProvider.toSymbol(operation).getName() )); } } MiddlewareStackStep.java000066400000000000000000000023441463735525100405700ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; /** * Represents the middleware stack step. */ public enum MiddlewareStackStep { INITIALIZE, BUILD, SERIALIZE, DESERIALIZE, FINALIZE; @Override public String toString() { switch (this) { case INITIALIZE: return "Initialize"; case BUILD: return "Build"; case SERIALIZE: return "Serialize"; case DESERIALIZE: return "Deserialize"; case FINALIZE: return "Finalize"; default: return "Unknown"; } } } OperationInterfaceGenerator.java000066400000000000000000000126431463735525100423240ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.PaginatedIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; 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.waiters.WaitableTrait; /** * Generates API client Interfaces as per API operation. */ public class OperationInterfaceGenerator implements GoIntegration { private final Map> mapOfClientInterfaceOperations = new HashMap<>(); /** * Returns name of an API client interface. * * @param operationSymbol Symbol of operation shape for which Api client interface is being generated. * @return name of the interface. */ public static String getApiClientInterfaceName(Symbol operationSymbol) { return String.format("%sAPIClient", operationSymbol.getName()); } @Override public void processFinalizedModel(GoSettings settings, Model model) { ServiceShape serviceShape = settings.getService(model); TopDownIndex topDownIndex = TopDownIndex.of(model); PaginatedIndex paginatedIndex = PaginatedIndex.of(model); Set listOfClientInterfaceOperations = new TreeSet<>(); // fetch operations for which paginators are generated topDownIndex.getContainedOperations(serviceShape).stream() .map(operationShape -> paginatedIndex.getPaginationInfo(serviceShape, operationShape)) .filter(Optional::isPresent) .map(Optional::get) .forEach(paginationInfo -> listOfClientInterfaceOperations.add(paginationInfo.getOperation().getId())); // fetch operations for which waitable trait is applied topDownIndex.getContainedOperations(serviceShape).stream() .filter(operationShape -> operationShape.hasTrait(WaitableTrait.class)) .forEach(operationShape -> listOfClientInterfaceOperations.add(operationShape.getId())); if (!listOfClientInterfaceOperations.isEmpty()) { mapOfClientInterfaceOperations.put(serviceShape.getId(), listOfClientInterfaceOperations); } } @Override public void writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator goDelegator ) { ShapeId serviceId = settings.getService(model).getId(); if (mapOfClientInterfaceOperations.containsKey(serviceId)) { Set listOfClientInterfaceOperations = mapOfClientInterfaceOperations.get(serviceId); listOfClientInterfaceOperations.forEach(shapeId -> { OperationShape operationShape = model.expectShape(shapeId, OperationShape.class); goDelegator.useShapeWriter(operationShape, writer -> { generateApiClientInterface(writer, model, symbolProvider, operationShape); }); }); } } private void generateApiClientInterface( GoWriter writer, Model model, SymbolProvider symbolProvider, OperationShape operationShape ) { Symbol contextSymbol = SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT) .build(); Symbol operationSymbol = symbolProvider.toSymbol(operationShape); Symbol interfaceSymbol = SymbolUtils.createValueSymbolBuilder(getApiClientInterfaceName(operationSymbol)) .build(); Symbol inputSymbol = symbolProvider.toSymbol(model.expectShape(operationShape.getInput().get())); Symbol outputSymbol = symbolProvider.toSymbol(model.expectShape(operationShape.getOutput().get())); writer.writeDocs(String.format("%s is a client that implements the %s operation.", interfaceSymbol.getName(), operationSymbol.getName())); writer.openBlock("type $T interface {", "}", interfaceSymbol, () -> { writer.write("$L($T, $P, ...func(*Options)) ($P, error)", operationSymbol.getName(), contextSymbol, inputSymbol, outputSymbol); }); writer.write(""); writer.write("var _ $T = (*Client)(nil)", interfaceSymbol); writer.write(""); } } Paginators.java000066400000000000000000000372371463735525100370110ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import static java.util.Collections.emptySet; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.CodegenUtils; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoValueAccessUtils; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; import software.amazon.smithy.go.codegen.trait.PagingExtensionTrait; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.PaginatedIndex; import software.amazon.smithy.model.knowledge.PaginationInfo; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.BooleanShape; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.traits.DocumentationTrait; /** * Implements support for PaginatedTrait. */ public class Paginators implements GoIntegration { public Set getAdditionalClientOptions() { return emptySet(); } @Override public void writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator goDelegator ) { ServiceShape serviceShape = settings.getService(model); PaginatedIndex paginatedIndex = PaginatedIndex.of(model); TopDownIndex topDownIndex = TopDownIndex.of(model); topDownIndex.getContainedOperations(serviceShape).stream() .map(operationShape -> paginatedIndex.getPaginationInfo(serviceShape, operationShape)) .filter(Optional::isPresent) .map(Optional::get) .forEach(paginationInfo -> { goDelegator.useShapeWriter(paginationInfo.getOperation(), writer -> { generateOperationPaginator(model, symbolProvider, writer, paginationInfo); }); }); } private void generateOperationPaginator( Model model, SymbolProvider symbolProvider, GoWriter writer, PaginationInfo paginationInfo ) { Symbol operationSymbol = symbolProvider.toSymbol(paginationInfo.getOperation()); Symbol interfaceSymbol = SymbolUtils.createValueSymbolBuilder( OperationInterfaceGenerator.getApiClientInterfaceName(operationSymbol) ).build(); Symbol paginatorSymbol = SymbolUtils.createPointableSymbolBuilder(String.format("%sPaginator", operationSymbol.getName())).build(); Symbol optionsSymbol = SymbolUtils.createPointableSymbolBuilder(String.format("%sOptions", paginatorSymbol.getName())).build(); writePaginatorOptions(writer, model, symbolProvider, paginationInfo, operationSymbol, optionsSymbol); writePaginator(writer, model, symbolProvider, paginationInfo, interfaceSymbol, paginatorSymbol, optionsSymbol); } private void writePaginator( GoWriter writer, Model model, SymbolProvider symbolProvider, PaginationInfo paginationInfo, Symbol interfaceSymbol, Symbol paginatorSymbol, Symbol optionsSymbol ) { var inputMember = symbolProvider.toMemberName(paginationInfo.getInputTokenMember()); var operation = paginationInfo.getOperation(); var pagingExtensionTrait = operation.getTrait(PagingExtensionTrait.class); var operationSymbol = symbolProvider.toSymbol(operation); var inputSymbol = symbolProvider.toSymbol(paginationInfo.getInput()); var inputTokenSymbol = symbolProvider.toSymbol(paginationInfo.getInputTokenMember()); var inputTokenShape = model.expectShape(paginationInfo.getInputTokenMember().getTarget()); GoPointableIndex pointableIndex = GoPointableIndex.of(model); writer.pushState(); writer.putContext("paginator", paginatorSymbol); writer.putContext("options", optionsSymbol); writer.putContext("client", interfaceSymbol); writer.putContext("input", inputSymbol); writer.putContext("token", inputTokenSymbol); writer.putContext("inputMember", inputMember); writer.writeDocs(String.format("%s is a paginator for %s", paginatorSymbol, operationSymbol.getName())); writer.write(""" type $paginator:T struct { options $options:T client $client:T params $input:P nextToken $token:P firstPage bool } """); Symbol newPagiantor = SymbolUtils.createValueSymbolBuilder(String.format("New%s", paginatorSymbol.getName())).build(); writer.putContext("newPaginator", newPagiantor); writer.writeDocs(String.format("%s returns a new %s", newPagiantor.getName(), paginatorSymbol.getName())); writer.openBlock("func $newPaginator:T(client $client:T, params $input:P, " + "optFns ...func($options:P)) $paginator:P {", "}", () -> { writer.write(""" if params == nil { params = &$input:T{} } options := $options:T{}"""); paginationInfo.getPageSizeMember().ifPresent(memberShape -> { GoValueAccessUtils.writeIfNonZeroValueMember(model, symbolProvider, writer, memberShape, "params", op -> { op = CodegenUtils.getAsValueIfDereferencable(pointableIndex, memberShape, op); writer.write("options.Limit = $L", op); }); }); writer.write(""" for _, fn := range optFns { fn(&options) } return &$paginator:T{ options: options, client: client, params: params, firstPage: true, nextToken: params.$inputMember:L, }"""); }).write(""); writer.writeDocs("HasMorePages returns a boolean indicating whether more pages are available"); writer.openBlock("func (p $paginator:P) HasMorePages() bool {", "}", () -> { writer.writeInline("return p.firstPage || "); Runnable checkNotNil = () -> writer.writeInline("p.nextToken != nil"); if (inputTokenShape.getType() == ShapeType.STRING) { writer.writeInline("("); checkNotNil.run(); writer.write(" && len(*p.nextToken) != 0 )"); } else { checkNotNil.run(); } }).write(""); var contextSymbol = SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT) .build(); var outputSymbol = symbolProvider.toSymbol(paginationInfo.getOutput()); var pageSizeMember = paginationInfo.getPageSizeMember(); writer.putContext("context", contextSymbol); writer.putContext("output", outputSymbol); writer.writeDocs(String.format("NextPage retrieves the next %s page.", operationSymbol.getName())); writer.openBlock("func (p $paginator:P) NextPage(ctx $context:T, optFns ...func(*Options)) " + "($output:P, error) {", "}", () -> { writer.putContext("errorf", SymbolUtils.createValueSymbolBuilder("Errorf", SmithyGoDependency.FMT).build()); writer.write(""" if !p.HasMorePages() { return nil, $errorf:T("no more pages available") } params := *p.params params.$inputMember:L = p.nextToken """); pageSizeMember.ifPresent(memberShape -> { if (pointableIndex.isPointable(memberShape)) { writer.write(""" var limit $P if p.options.Limit > 0 { limit = &p.options.Limit } params.$L = limit """, symbolProvider.toSymbol(memberShape), symbolProvider.toMemberName(memberShape)); } else { writer.write("params.$L = p.options.Limit", symbolProvider.toMemberName(memberShape)) .write(""); } }); var optFns = GoWriter.ChainWritable.of( getAdditionalClientOptions().stream() .map(it -> goTemplate("$T,", it)) .toList() ).compose(false); writer.write(""" optFns = append([]func(*Options) { $W }, optFns...) result, err := p.client.$L(ctx, ¶ms, optFns...) if err != nil { return nil, err } p.firstPage = false """, optFns, operationSymbol.getName()); var outputMemberPath = paginationInfo.getOutputTokenMemberPath(); var tokenMember = outputMemberPath.get(outputMemberPath.size() - 1); Consumer setNextTokenFromOutput = (container) -> { writer.write("p.nextToken = $L", container + "." + symbolProvider.toMemberName(tokenMember)); }; for (int i = outputMemberPath.size() - 2; i >= 0; i--) { var memberShape = outputMemberPath.get(i); Consumer inner = setNextTokenFromOutput; setNextTokenFromOutput = (container) -> { GoValueAccessUtils.writeIfNonZeroValueMember(model, symbolProvider, writer, memberShape, container, inner); }; } { final Consumer inner = setNextTokenFromOutput; setNextTokenFromOutput = s -> { if (outputMemberPath.size() > 1) { writer.write("p.nextToken = nil"); } inner.accept(s); }; } { final Consumer setToken = setNextTokenFromOutput; writer.write("prevToken := p.nextToken"); Optional moreResults = pagingExtensionTrait .flatMap(PagingExtensionTrait::getMoreResults); if (moreResults.isPresent()) { MemberShape memberShape = moreResults.get(); model.expectShape(memberShape.getTarget(), BooleanShape.class); // Must be boolean writer.write("p.nextToken = nil"); String memberName = symbolProvider.toMemberName(memberShape); if (pointableIndex.isNillable(memberShape.getTarget())) { writer.openBlock("if result.$L != nil && *result.$L {", "}", memberName, memberName, () -> setToken.accept("result")); } else { writer.openBlock("if result.$L {", "}", memberName, () -> setToken.accept("result")); } } else { setToken.accept("result"); } } writer.write(""); if (inputTokenShape.isStringShape()) { writer.write(""" if p.options.StopOnDuplicateToken && prevToken != nil && p.nextToken != nil && *prevToken == *p.nextToken { p.nextToken = nil } """); } else { writer.write("_ = prevToken").write(""); } writer.write("return result, nil"); }); writer.popState(); } private void writePaginatorOptions( GoWriter writer, Model model, SymbolProvider symbolProvider, PaginationInfo paginationInfo, Symbol operationSymbol, Symbol optionsSymbol ) { writer.writeDocs(String.format("%s is the paginator options for %s", optionsSymbol.getName(), operationSymbol.getName())); writer.openBlock("type $T struct {", "}", optionsSymbol, () -> { paginationInfo.getPageSizeMember().ifPresent(memberShape -> { memberShape.getMemberTrait(model, DocumentationTrait.class).ifPresent(documentationTrait -> { writer.writeDocs(documentationTrait.getValue()); }); writer.write("Limit $T", symbolProvider.toSymbol(memberShape)); writer.write(""); }); if (model.expectShape(paginationInfo.getInputTokenMember().getTarget()).isStringShape()) { writer.writeDocs("Set to true if pagination should stop if the service returns a pagination token that " + "matches the most recent token provided to the service."); writer.write("StopOnDuplicateToken bool"); } }); writer.write(""); } } ProtocolGenerator.java000066400000000000000000000553471463735525100403540ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.ApplicationProtocol; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.Synthetic; import software.amazon.smithy.go.codegen.auth.AuthGenerator; import software.amazon.smithy.go.codegen.endpoints.EndpointResolutionGenerator; import software.amazon.smithy.go.codegen.endpoints.FnGenerator; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.ExpectationNotMetException; import software.amazon.smithy.model.shapes.OperationShape; 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.Trait; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; import software.amazon.smithy.utils.CaseUtils; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.StringUtils; /** * Smithy protocol code generators. */ public interface ProtocolGenerator { /** * Sanitizes the name of the protocol so it can be used as a symbol * in Go. * *

* For example, the default implementation converts "." to "_", * and converts "-" to become camelCase separated words. This means * that "aws.rest-json-1.1" becomes "Aws_RestJson1_1". * * @param name Name of the protocol to sanitize. * @return Returns the sanitized name. */ static String getSanitizedName(String name) { name = name.replaceAll("(\\s|\\.|-)+", "_"); return CaseUtils.toCamelCase(name, true, '_'); } /** * Gets the supported protocol {@link ShapeId}. * * @return Returns the protocol supported */ ShapeId getProtocol(); default String getProtocolName() { ShapeId protocol = getProtocol(); String prefix = protocol.getNamespace(); int idx = prefix.indexOf('.'); if (idx != -1) { prefix = prefix.substring(0, idx); } return CaseUtils.toCamelCase(prefix) + getSanitizedName(protocol.getName()); } /** * Creates an application protocol for the generator. * * @return Returns the created application protocol. */ ApplicationProtocol getApplicationProtocol(); /** * Determines if two protocol generators are compatible at the * application protocol level, meaning they both use HTTP, or MQTT * for example. * *

* Two protocol implementations are considered compatible if the * {@link ApplicationProtocol#equals} method of {@link #getApplicationProtocol} * returns true when called with {@code other}. The default implementation * should work for most interfaces, but may be overridden for more in-depth * handling of things like minor version incompatibilities. * *

* By default, if the application protocols are considered equal, then * {@code other} is returned. * * @param service Service being generated. * @param protocolGenerators Other protocol generators that are being generated. * @param other Protocol generator to resolve against. * @return Returns the resolved application protocol object. */ default ApplicationProtocol resolveApplicationProtocol( ServiceShape service, Collection protocolGenerators, ApplicationProtocol other) { if (!getApplicationProtocol().equals(other)) { String protocolNames = protocolGenerators.stream() .map(ProtocolGenerator::getProtocol) .map(Trait::getIdiomaticTraitName) .sorted() .collect(Collectors.joining(", ")); throw new CodegenException(String.format( "All of the protocols generated for a service must be runtime compatible, but " + "protocol `%s` is incompatible with other application protocols: [%s]. Please pick a " + "set of compatible protocols using the `protocols` option when generating %s.", getProtocol(), protocolNames, service.getId())); } return other; } /** * Generates any standard code for service request/response serde. * * @param context Serde context. */ default void generateSharedSerializerComponents(GenerationContext context) { } /** * Generates the code used to serialize the shapes of a service * for requests. * * @param context Serialization context. */ void generateRequestSerializers(GenerationContext context); /** * Generates any standard code for service response deserialization. * * @param context Serde context. */ default void generateSharedDeserializerComponents(GenerationContext context) { } /** * Generates the code used to deserialize the shapes of a service * for responses. * * @param context Deserialization context. */ void generateResponseDeserializers(GenerationContext context); /** * Generates the code for validating the generated protocol's serializers and * deserializers. * * @param context Generation context */ default void generateProtocolTests(GenerationContext context) { } /** * Generates the name of a serializer function for shapes of a service. * * @param shape The shape the serializer function is being generated for. * @param service The service shape. * @param protocol Name of the protocol being generated. * @return Returns the generated function name. */ static String getOperationHttpBindingsSerFunctionName(Shape shape, ServiceShape service, String protocol) { return protocol + "_serializeOpHttpBindings" + StringUtils.capitalize(shape.getId().getName(service)); } /** * Generates the name of a deserializer function for shapes of a service. * * @param shape The shape the deserializer function is being generated for. * @param service The service shape. * @param protocol Name of the protocol being generated. * @return Returns the generated function name. */ static String getOperationHttpBindingsDeserFunctionName(Shape shape, ServiceShape service, String protocol) { return protocol + "_deserializeOpHttpBindings" + StringUtils.capitalize(shape.getId().getName(service)); } /** * Generates the name of a serializer function for shapes of a service. * * @param shape The shape the serializer function is being generated for. * @param service The service shape within which the deserialized shape is * enclosed. * @param protocol Name of the protocol being generated. * @return Returns the generated function name. */ static String getDocumentSerializerFunctionName(Shape shape, ServiceShape service, String protocol) { String name = shape.getId().getName(service); String extra = ""; if (shape.hasTrait(Synthetic.class)) { extra = "Op"; } return protocol + "_serialize" + extra + "Document" + StringUtils.capitalize(name); } /** * Generates the name of a deserializer function for shapes of a service. * * @param shape The shape the deserializer function is being generated for. * @param service The service shape within which the deserialized shape is * enclosed. * @param protocol Name of the protocol being generated. * @return Returns the generated function name. */ static String getDocumentDeserializerFunctionName(Shape shape, ServiceShape service, String protocol) { String name = shape.getId().getName(service); String extra = ""; if (shape.hasTrait(Synthetic.class)) { extra = "Op"; } return protocol + "_deserialize" + extra + "Document" + StringUtils.capitalize(name); } static String getOperationErrorDeserFunctionName(OperationShape shape, ServiceShape service, String protocol) { return protocol + "_deserializeOpError" + StringUtils.capitalize(shape.getId().getName(service)); } /** * Generates the name of an error deserializer function for shapes of a service. * * @param shape The error structure shape for which deserializer name is * being generated. * @param service The service enclosing the service shape. * @param protocol Name of the protocol being generated. * @return Returns the generated function name. */ static String getErrorDeserFunctionName(StructureShape shape, ServiceShape service, String protocol) { String name = shape.getId().getName(service); return protocol + "_deserializeError" + StringUtils.capitalize(name); } static String getSerializeMiddlewareName(ShapeId operationShapeId, ServiceShape service, String protocol) { return protocol + "_serializeOp" + operationShapeId.getName(service); } static String getDeserializeMiddlewareName(ShapeId operationShapeId, ServiceShape service, String protocol) { return protocol + "_deserializeOp" + operationShapeId.getName(service); } /** * Returns a map of error names to their {@link ShapeId}. * * @param context the generation context * @param operation the operation shape to retrieve errors for * @return map of error names to {@link ShapeId} */ default Map getOperationErrors(GenerationContext context, OperationShape operation) { return HttpProtocolGeneratorUtils.getOperationErrors(context, operation); } /** * Generates the UnmarshalSmithyDocument function body of the service's internal * documentMarshaler type. *

* The document marshaler type is expected to handle user provided Go types and * convert them to protocol documents. *

* The default implementation will throw a {@code CodegenException} if not * implemented. * *

{@code
     * type documentMarshaler struct {
     *     value interface{}
     * }
     *
     * // ...
     *
     * func (m *documentMarshaler) UnmarshalSmithyDocument(v interface{}) error {
     *      // Generated code from generateProtocolDocumentMarshalerUnmarshalDocument
     * }
     * }
* * @param context the generation context. */ default void generateProtocolDocumentMarshalerUnmarshalDocument(GenerationContext context) { throw new CodegenException("document types not implemented for " + this.getProtocolName() + " protocol"); } /** * Generates the UnmarshalSmithyDocument function body of the service's internal * documentMarshaler type. *

* The document marshaler type is expected to handle user provided Go types and * convert them to protocol documents. *

* The default implementation will throw a {@code CodegenException} if not * implemented. * *

{@code
     * type documentMarshaler struct {
     *     value interface{}
     * }
     *
     * // ...
     *
     * func (m *documentMarshaler) MarshalSmithyDocument() ([]byte, error) {
     *      // Generated code from generateProtocolDocumentMarshalerMarshalDocument
     * }
     * }
* * @param context the generation context. */ default void generateProtocolDocumentMarshalerMarshalDocument(GenerationContext context) { throw new CodegenException("document types not implemented for " + this.getProtocolName() + " protocol"); } /** * Generates the UnmarshalSmithyDocument function body of the service's internal * documentUnmarshaler type. *

* The document unmarshaler type is expected to handle protocol documents * received from the service and provide the * ability to unmarshal or round-trip the document. *

* The default implementation will throw a {@code CodegenException} if not * implemented. * *

{@code
     * type documentUnmarshaler struct {
     *     value interface{}
     * }
     *
     * // ...
     *
     * func (m *documentUnmarshaler) UnmarshalSmithyDocument(v interface{}) error {
     *      // Generated code from generateProtocolDocumentUnmarshalerUnmarshalDocument
     * }
     * }
* * @param context the generation context. */ default void generateProtocolDocumentUnmarshalerUnmarshalDocument(GenerationContext context) { throw new CodegenException("document types not implemented for " + this.getProtocolName() + " protocol"); } /** * Generates the MarshalSmithyDocument function body of the service's internal * documentUnmarshaler type. *

* The document unmarshaler type is expected to handle protocol documents * received from the service and provide the * ability to unmarshal or round-trip the document. *

* The default implementation will throw a {@code CodegenException} if not * implemented. * *

{@code
     * type documentUnmarshaler struct {
     *     value interface{}
     * }
     *
     * // ...
     *
     * func (m *documentUnmarshaler) MarshalSmithyDocument() ([]byte, error) {
     *      // Generated code from generateProtocolDocumentUnmarshalerMarshalDocument
     * }
     * }
* * @param context the generation context. */ default void generateProtocolDocumentUnmarshalerMarshalDocument(GenerationContext context) { throw new CodegenException("document types not implemented for " + this.getProtocolName() + " protocol"); } /** * Generate the internal constructor function body for the service's internal * documentMarshaler type. * *
{@code
     * func NewDocumentMarshaler(v interface{}) Interface {
     *     return &documentMarshaler{
     *         value: v,
     *     }
     * }
     * }
* * @param context the generation context. * @param marshalerSymbol the symbol for the {@code documentMarshaler} type. */ default void generateNewDocumentMarshaler(GenerationContext context, Symbol marshalerSymbol) { GoWriter writer = context.getWriter().get(); writer.openBlock("return &$T{", "}", marshalerSymbol, () -> { writer.write("value: v,"); }); } /** * Generate the internal constructor function body for the service's internal * documentUnmarshaler type. * *
{@code
     * func NewDocumentUnmarshaler(v interface{}) Interface {
     *     return &documentUnmarshaler{
     *         value: v,
     *     }
     * }
     * }
* * @param context the generation context. * @param unmarshalerSymbol the symbol for the {@code documentUnmarshaler} type. */ default void generateNewDocumentUnmarshaler(GenerationContext context, Symbol unmarshalerSymbol) { GoWriter writer = context.getWriter().get(); writer.openBlock("return &$T{", "}", unmarshalerSymbol, () -> { writer.write("value: v,"); }); } /** * Returns an error code for an error shape. Defaults to error shape name as * error code. * * @param service the service enclosure for the error shape. * @param errorShape the error shape for which error code is retrieved. * @return the error code associated with the provided shape. * @throws ExpectationNotMetException if provided shape is not modeled with an * {@link ErrorTrait}. */ default String getErrorCode(ServiceShape service, StructureShape errorShape) { errorShape.expectTrait(ErrorTrait.class); return errorShape.getId().getName(service); } /** * Generate specific components for the protocol's event stream implementation. * These components * types should provide implementations that satisfy the reader and writer event * stream interfaces. * * @param context the generation context. */ default void generateEventStreamComponents(GenerationContext context) { } /** * Generate all components necessary for Endpoint Rules Engine endpoint * resolution. * * @param context the generation context. */ default void generateEndpointResolution(GenerationContext context) { var generator = new EndpointResolutionGenerator(new FnGenerator.DefaultFnProvider()); generator.generate(context); } /** * Generate the tests for Endpoint Rules Engine endpoint resolution. * * @param context the generation context. */ default void generateEndpointResolutionTests(GenerationContext context) { var generator = new EndpointResolutionGenerator(new FnGenerator.DefaultFnProvider()); generator.generateTests(context); } /** * Generates smithy client auth components. * * @param context The generation context. */ default void generateAuth(GenerationContext context) { new AuthGenerator(context).generate(); } /** * Context object used for service serialization and deserialization. */ class GenerationContext { private final GoSettings settings; private final Model model; private final ServiceShape service; private final SymbolProvider symbolProvider; private final GoWriter writer; private final List integrations; private final String protocolName; private final GoDelegator delegator; private GenerationContext(Builder builder) { this.settings = SmithyBuilder.requiredState("settings", builder.settings); this.model = SmithyBuilder.requiredState("model", builder.model); this.service = SmithyBuilder.requiredState("service", builder.service); this.symbolProvider = SmithyBuilder.requiredState("symbolProvider", builder.symbolProvider); this.writer = builder.writer; this.integrations = SmithyBuilder.requiredState("integrations", builder.integrations); this.protocolName = SmithyBuilder.requiredState("protocolName", builder.protocolName); this.delegator = SmithyBuilder.requiredState("delegator", builder.delegator); } public static Builder builder() { return new Builder(); } public Builder toBuilder() { return builder() .settings(this.settings) .model(this.model) .service(this.service) .symbolProvider(this.symbolProvider) .writer(this.writer) .integrations(this.integrations) .protocolName(this.protocolName) .delegator(this.delegator); } public GoSettings getSettings() { return settings; } public Model getModel() { return model; } public ServiceShape getService() { return service; } public EndpointRuleSet getEndpointRules() { return EndpointRuleSet.fromNode(service.expectTrait(EndpointRuleSetTrait.class).getRuleSet()); } public SymbolProvider getSymbolProvider() { return symbolProvider; } public Optional getWriter() { return Optional.ofNullable(writer); } public GoDelegator getDelegator() { return delegator; } public List getIntegrations() { return integrations; } public String getProtocolName() { return protocolName; } public static class Builder implements SmithyBuilder { private GoSettings settings; private Model model; private ServiceShape service; private SymbolProvider symbolProvider; private GoWriter writer; private final List integrations = new ArrayList<>(); private String protocolName; private GoDelegator delegator; public Builder settings(GoSettings settings) { this.settings = settings; return this; } public Builder model(Model model) { this.model = model; return this; } public Builder service(ServiceShape service) { this.service = service; return this; } public Builder symbolProvider(SymbolProvider symbolProvider) { this.symbolProvider = symbolProvider; return this; } public Builder writer(GoWriter writer) { this.writer = writer; return this; } public Builder integrations(Collection integrations) { this.integrations.clear(); this.integrations.addAll(integrations); return this; } public Builder protocolName(String protocolName) { this.protocolName = protocolName; return this; } public Builder delegator(GoDelegator delegator) { this.delegator = delegator; return this; } @Override public GenerationContext build() { return new GenerationContext(this); } } } } ProtocolUtils.java000066400000000000000000000210101463735525100375020ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.Set; import java.util.TreeSet; import java.util.function.Consumer; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.MiddlewareIdentifier; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator.GenerationContext; import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.OperationIndex; import software.amazon.smithy.model.neighbor.RelationshipType; import software.amazon.smithy.model.neighbor.Walker; 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.ShapeId; import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.utils.SetUtils; /** * Utility functions for protocol generation. */ public final class ProtocolUtils { public static final MiddlewareIdentifier OPERATION_SERIALIZER_MIDDLEWARE_ID = MiddlewareIdentifier .string("OperationSerializer"); public static final MiddlewareIdentifier OPERATION_DESERIALIZER_MIDDLEWARE_ID = MiddlewareIdentifier .string("OperationDeserializer"); private static final Set REQUIRES_SERDE = SetUtils.of( ShapeType.MAP, ShapeType.LIST, ShapeType.SET, ShapeType.DOCUMENT, ShapeType.STRUCTURE, ShapeType.UNION); private static final Set MEMBER_RELATIONSHIPS = SetUtils.of( RelationshipType.STRUCTURE_MEMBER, RelationshipType.UNION_MEMBER, RelationshipType.LIST_MEMBER, RelationshipType.SET_MEMBER, RelationshipType.MAP_VALUE, RelationshipType.MEMBER_TARGET ); private ProtocolUtils() { } /** * Resolves the entire set of shapes that will require serde given an initial set of shapes. * * @param model the model * @param shapes the shapes to walk and resolve additional required serializers, deserializers for * @return the complete set of shapes requiring serializers, deserializers */ public static Set resolveRequiredDocumentShapeSerde(Model model, Set shapes) { Set processed = new TreeSet<>(); Set resolvedShapes = new TreeSet<>(); Walker walker = new Walker(model); shapes.forEach(shape -> { processed.add(shape.getId()); resolvedShapes.add(shape); walker.iterateShapes(shape, relationship -> MEMBER_RELATIONSHIPS.contains( relationship.getRelationshipType())) .forEachRemaining(walkedShape -> { // MemberShape type itself is not what we are interested in if (walkedShape.getType() == ShapeType.MEMBER) { return; } if (processed.contains(walkedShape.getId())) { return; } if (requiresDocumentSerdeFunction(shape)) { resolvedShapes.add(walkedShape); processed.add(walkedShape.getId()); } }); }); return resolvedShapes; } /** * Determines whether a document serde function is required for the given shape. *

* The following shape types will require a serde function: maps, lists, sets, documents, structures, and unions. * * @param shape the shape * @return true if the shape requires a serde function */ public static boolean requiresDocumentSerdeFunction(Shape shape) { return REQUIRES_SERDE.contains(shape.getType()); } /** * Gets the operation input as a structure shape or throws an exception. * * @param model The model that contains the operation. * @param operation The operation to get the input from. * @return The operation's input as a structure shape. */ public static StructureShape expectInput(Model model, OperationShape operation) { return OperationIndex.of(model).getInput(operation) .orElseThrow(() -> new CodegenException( "Expected input shape for operation " + operation.getId().toString())); } /** * Gets the operation output as a structure shape or throws an exception. * * @param model The model that contains the operation. * @param operation The operation to get the output from. * @return The operation's output as a structure shape. */ public static StructureShape expectOutput(Model model, OperationShape operation) { return OperationIndex.of(model).getOutput(operation) .orElseThrow(() -> new CodegenException( "Expected output shape for operation " + operation.getId().toString())); } /** * Wraps the protocol's delegation function changing the destination variable to a double pointer if the * shape type is not pointable. * * @param context generation context * @param writer go writer * @param member shape to determine if pointable * @param origDestOperand original variable name * @param lambda runnable */ public static void writeDeserDelegateFunction( GenerationContext context, GoWriter writer, MemberShape member, String origDestOperand, Consumer lambda ) { Shape targetShape = context.getModel().expectShape(member.getTarget()); Shape container = context.getModel().expectShape(member.getContainer()); boolean withAddr = !GoPointableIndex.of(context.getModel()).isPointable(member) && GoPointableIndex.of(context.getModel()).isPointable(targetShape); boolean isMap = container.getType() == ShapeType.MAP; String destOperand = origDestOperand; if (isMap) { writer.write("mapVar := $L", origDestOperand); destOperand = "mapVar"; } if (withAddr) { writer.write("destAddr := &$L", destOperand); destOperand = "destAddr"; } lambda.accept(destOperand); if (isMap || withAddr) { if (withAddr) { destOperand = "*" + destOperand; } writer.write("$L = $L", origDestOperand, destOperand); } } /** * Writes helper variables for the delegation function to ensure that map values are safely delegated down * each level. * * @param context generation context * @param writer go writer * @param member shape to determine if pointable * @param origDestOperand original variable name * @param lambda runnable */ public static void writeSerDelegateFunction( GenerationContext context, GoWriter writer, MemberShape member, String origDestOperand, Consumer lambda ) { Shape targetShape = context.getModel().expectShape(member.getTarget()); Shape container = context.getModel().expectShape(member.getContainer()); boolean withAddr = !GoPointableIndex.of(context.getModel()).isPointable(member) && GoPointableIndex.of(context.getModel()).isPointable(targetShape); boolean isMap = container.getType() == ShapeType.MAP; String destOperand = origDestOperand; if (isMap && withAddr) { writer.write("mapVar := $L", origDestOperand); destOperand = "mapVar"; } String acceptVar = destOperand; if (withAddr) { acceptVar = "&" + destOperand; } lambda.accept(acceptVar); } } RequiresLengthTraitSupport.java000066400000000000000000000044771463735525100422440ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.List; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.traits.RequiresLengthTrait; import software.amazon.smithy.utils.ListUtils; /** * Adds a runtime plugin to support requires-length trait behavior. */ public class RequiresLengthTraitSupport implements GoIntegration { @Override public byte getOrder() { return 127; } @Override public List getClientPlugins() { return ListUtils.of( RuntimeClientPlugin.builder() .operationPredicate(this::hasRequiresLengthTrait) .registerMiddleware(MiddlewareRegistrar.builder() .resolvedFunction(SymbolUtils.createValueSymbolBuilder( "ValidateContentLengthHeader", SmithyGoDependency.SMITHY_HTTP_TRANSPORT).build()) .build()) .build() ); } // return true if operation shape has a streaming blob member decorated with `requiresLength` trait. private boolean hasRequiresLengthTrait(Model model, ServiceShape service, OperationShape operation) { for (MemberShape member : operation.members()) { if (member.hasTrait(RequiresLengthTrait.class)) { return true; } } return false; } } RuntimeClientPlugin.java000066400000000000000000000474551463735525100406460ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.BiPredicate; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.auth.AuthParameter; import software.amazon.smithy.go.codegen.auth.AuthParametersResolver; 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.utils.SmithyBuilder; import software.amazon.smithy.utils.ToSmithyBuilder; /** * Represents a runtime plugin for a client that hooks into various aspects * of Go code generation, including adding configuration settings * to clients and middleware plugins to both clients and commands. * *

These runtime client plugins are registered through the * {@link GoIntegration} SPI and applied to the code generator at * build-time. */ public final class RuntimeClientPlugin implements ToSmithyBuilder { private final BiPredicate servicePredicate; private final OperationPredicate operationPredicate; private final Set configFields; private final Set configFieldResolvers; private final Set clientMembers; private final Set clientMemberResolvers; private final Set authParameters; private final Set authParameterResolvers; private final MiddlewareRegistrar registerMiddleware; private final Map endpointBuiltinBindings; private final Map authSchemeDefinitions; private final Map shapeDeserializers; private RuntimeClientPlugin(Builder builder) { operationPredicate = builder.operationPredicate; servicePredicate = builder.servicePredicate; configFields = builder.configFields; registerMiddleware = builder.registerMiddleware; clientMembers = builder.clientMembers; clientMemberResolvers = builder.clientMemberResolvers; authParameters = builder.authParameters; authParameterResolvers = builder.authParameterResolvers; configFieldResolvers = builder.configFieldResolvers; endpointBuiltinBindings = builder.endpointBuiltinBindings; authSchemeDefinitions = builder.authSchemeDefinitions; shapeDeserializers = builder.shapeDeserializers; } @FunctionalInterface public interface OperationPredicate { /** * Tests if middleware is applied to an individual operation. * * @param model Model the operation belongs to. * @param service Service the operation belongs to. * @param operation Operation to test. * @return Returns true if middleware should be applied to the operation. */ boolean test(Model model, ServiceShape service, OperationShape operation); } /** * Gets the config fields that will be added to the client config by this plugin. * @return the config field resolvers. */ public Set getConfigFieldResolvers() { return configFieldResolvers; } /** * Gets the client members that will be added to the client structure by this plugin. * @return the client member resolvers. */ public Set getClientMemberResolvers() { return clientMemberResolvers; } /** * Gets the auth parameters that will be added by this plugin. * @return the auth parameters. */ public Set getAuthParameters() { return authParameters; } /** * Gets the auth parameter resolvers that will be added by this plugin. * @return the auth parameter resolvers. */ public Set getAuthParameterResolvers() { return authParameterResolvers; } /** * Gets the endpoint builtin bindings that will be rendered by this plugin. * @return the bindings. */ public Map getEndpointBuiltinBindings() { return endpointBuiltinBindings; } /** * Gets the registered auth scheme codegen definitions. * @return the definitions. */ public Map getAuthSchemeDefinitions() { return authSchemeDefinitions; } /** * Gets the registered shape deserializers. * @return the deserializers. */ public Map getShapeDeserializers() { return shapeDeserializers; } /** * Gets the optionally present middleware registrar object that resolves to middleware registering function. * * @return Returns the optionally present MiddlewareRegistrar object. */ public Optional registerMiddleware() { return Optional.ofNullable(registerMiddleware); } /** * Returns true if this plugin applies to the given service. * *

By default, a plugin applies to all services but not to specific * commands. You an configure a plugin to apply only to a subset of * services (for example, only apply to a known service or a service * with specific traits) or to no services at all (for example, if * the plugin is meant to by command-specific and not on every * command executed by the service). * * @param model The model the service belongs to. * @param service Service shape to test against. * @return Returns true if the plugin is applied to the given service. * @see #matchesOperation(Model, ServiceShape, OperationShape) */ public boolean matchesService(Model model, ServiceShape service) { return servicePredicate.test(model, service); } /** * Returns true if this plugin applies to the given operation. * * @param model Model the operation belongs to. * @param service Service the operation belongs to. * @param operation Operation to test against. * @return Returns true if the plugin is applied to the given operation. * @see #matchesService(Model, ServiceShape) */ public boolean matchesOperation(Model model, ServiceShape service, OperationShape operation) { return operationPredicate.test(model, service, operation); } /** * Gets the config fields that will be added to the client config by this plugin. * *

Each config field will be added to the client's Config object and will * result in a corresponding getter method being added to the config. E.g.: *

* type ClientOptions struct { * // My docs. * MyField string * } *

* func (o ClientOptions) GetMyField() string { * return o.MyField * } * * @return Returns the config fields to add to the client config. */ public Set getConfigFields() { return configFields; } /** * Gets the client member fields that will be added to the client structure by this plugin. * *

Each client member field will be added to the client's structure. * E.g.: *

* type Client struct { * * options Options * * // My cache. * cache map[string]string * } *

* * @return Returns the client members to add to the client structure. */ public Set getClientMembers() { return clientMembers; } public static Builder builder() { return new Builder(); } @Override public SmithyBuilder toBuilder() { return builder() .clientMemberResolvers(clientMemberResolvers) .configFieldResolvers(configFieldResolvers) .servicePredicate(servicePredicate) .operationPredicate(operationPredicate) .registerMiddleware(registerMiddleware); } /** * Builds a {@code RuntimeClientPlugin}. */ public static final class Builder implements SmithyBuilder { private BiPredicate servicePredicate = (model, service) -> true; private OperationPredicate operationPredicate = (model, service, operation) -> false; private Set configFields = new HashSet<>(); private Set configFieldResolvers = new HashSet<>(); private Set clientMembers = new HashSet<>(); private Set clientMemberResolvers = new HashSet<>(); private Set authParameters = new HashSet<>(); private Set authParameterResolvers = new HashSet<>(); private Map endpointBuiltinBindings = new HashMap<>(); private MiddlewareRegistrar registerMiddleware; private Map authSchemeDefinitions = new HashMap<>(); private Map shapeDeserializers = new HashMap<>(); @Override public RuntimeClientPlugin build() { return new RuntimeClientPlugin(this); } /** * Registers middleware into the operation middleware stack. * * @param registerMiddleware resolved middleware registrar to set. * @return Returns the builder. */ public Builder registerMiddleware(MiddlewareRegistrar registerMiddleware) { this.registerMiddleware = registerMiddleware; return this; } /** * Sets a predicate that determines if the plugin applies to a * specific operation. * *

When this method is called, the {@code servicePredicate} is * automatically configured to return false for every service. * *

By default, a plugin applies globally to a service, which thereby * applies to every operation when the middleware stack is copied. * * @param operationPredicate Operation matching predicate. * @return Returns the builder. * @see #servicePredicate(BiPredicate) */ public Builder operationPredicate(OperationPredicate operationPredicate) { this.operationPredicate = Objects.requireNonNull(operationPredicate); servicePredicate = (model, service) -> false; return this; } /** * Configures a predicate that makes a plugin only apply to a set of * operations that match one or more of the set of given shape names, * and ensures that the plugin is not applied globally to services. * *

By default, a plugin applies globally to a service, which thereby * applies to every operation when the middleware stack is copied. * * @param operationNames Set of operation names. * @return Returns the builder. */ public Builder appliesOnlyToOperations(Set operationNames) { operationPredicate((model, service, operation) -> operationNames.contains(operation.getId().getName())); return servicePredicate((model, service) -> false); } /** * Configures a predicate that applies the plugin to a service if the * predicate matches a given model and service. * *

When this method is called, the {@code operationPredicate} is * automatically configured to return false for every operation, * causing the plugin to only apply to services and not to individual * operations. * *

By default, a plugin applies globally to a service, which * thereby applies to every operation when the middleware stack is * copied. Setting a custom service predicate is useful for plugins * that should only be applied to specific services or only applied * at the operation level. * * @param servicePredicate Service predicate. * @return Returns the builder. */ public Builder servicePredicate(BiPredicate servicePredicate) { this.servicePredicate = Objects.requireNonNull(servicePredicate); operationPredicate = (model, service, operation) -> false; return this; } /** * Sets the config fields that will be added to the client config by this plugin. * *

Each config field will be added to the client's Config object and will * result in a corresponding getter method being added to the config. E.g.: *

* type ClientOptions struct { * // My docs. * MyField string * } *

* func (o ClientOptions) GetMyField() string { * return o.MyField * } * * @param configFields The config fields to add to the client config. * @return Returns the builder. */ public Builder configFields(Collection configFields) { this.configFields = new HashSet<>(configFields); return this; } /** * Adds a config field that will be added to the client config by this plugin. * *

Each config field will be added to the client's Config object and will * result in a corresponding getter method being added to the config. E.g.: *

* type ClientOptions struct { * // My docs. * MyField string * } *

* func (o ClientOptions) GetMyField() string { * return o.MyField * } * * @param configField The config field to add to the client config. * @return Returns the builder. */ public Builder addConfigField(ConfigField configField) { this.configFields.add(configField); return this; } /** * Sets the config field resolvers that will be added to the client by this plugin. * * @param configFieldResolvers The config field resolvers. * @return Returns the builder. */ public Builder configFieldResolvers(Collection configFieldResolvers) { this.configFieldResolvers = new HashSet<>(configFieldResolvers); return this; } /** * Adds a config field resolver that will be added to the client by this plugin. * * @param configFieldResolver The config field resolver. * @return Returns the builder. */ public Builder addConfigFieldResolver(ConfigFieldResolver configFieldResolver) { this.configFieldResolvers.add(configFieldResolver); return this; } /** * Sets the client member fields that will be added to the client struct * by this plugin. * *

Each client member field will be added to the client's struct. * E.g.: *

* type Client struct { * option Options * * // My cache added using plugin * cache map[string]string * } *

* * @param clientMembers The client members to add on the client. * @return Returns the builder. */ public Builder clientMembers(Collection clientMembers) { this.clientMembers = new HashSet<>(clientMembers); return this; } /** * Adds a client member that will be added to the client structure by this plugin. * *

Each client member field will be added to the client's structure. * E.g.: *

* type Client struct { * option Options * * // my cache added using plugin * cache map[string]string * } * * @param clientMember The clientMember to add to the client structure. * @return Returns the builder. */ public Builder addClientMember(ClientMember clientMember) { this.clientMembers.add(clientMember); return this; } /** * Sets the client member resolvers that will be added to the client by this plugin. * * @param clientMemberResolvers The client member resolvers. * @return Returns the builder. */ public Builder clientMemberResolvers(Collection clientMemberResolvers) { this.clientMemberResolvers = new HashSet<>(clientMemberResolvers); return this; } /** * Adds a client member resolver that will be added to the client by this plugin. * * @param clientMemberResolver The client member resolver. * @return Returns the builder. */ public Builder addClientMemberResolver(ClientMemberResolver clientMemberResolver) { this.clientMemberResolvers.add(clientMemberResolver); return this; } /** * Adds a field to the auth parameters. * * @param param The field. * @return Returns the builder. */ public Builder addAuthParameter(AuthParameter param) { this.authParameters.add(param); return this; } /** * Adds a resolver for fields on auth parameters. * * @param resolver The auth field resolver. * @return Returns the builder. */ public Builder addAuthParameterResolver(AuthParametersResolver resolver) { this.authParameterResolvers.add(resolver); return this; } /** * Adds a binding for an endpoint parameter builtin. * @param name The name of the builtin. * @param binding The writable binding. * @return Returns the builder. */ public Builder addEndpointBuiltinBinding(String name, GoWriter.Writable binding) { this.endpointBuiltinBindings.put(name, binding); return this; } /** * Registers a codegen definition for a modeled auth scheme. * @param schemeId The scheme id. * @param definition The codegen definition. * @return Returns the builder. */ public Builder addAuthSchemeDefinition(ShapeId schemeId, AuthSchemeDefinition definition) { this.authSchemeDefinitions.put(schemeId, definition); return this; } /** * Registers a codegen definition for a custom shape deserializer. This feature is currently only supported for * overriding deserialization in HTTP bindings. * @param id The shape id. * @param deserializer The deserializer symbol. The written code MUST be a function which accepts the * corresponding type for the shape and returns (*type, error) accordingly. * @return Returns the builder. */ public Builder addShapeDeserializer(ShapeId id, Symbol deserializer) { this.shapeDeserializers.put(id, deserializer); return this; } } } ValidationGenerator.java000066400000000000000000000450511463735525100406340ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ /* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.function.Consumer; import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.CodegenUtils; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.MiddlewareIdentifier; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.go.codegen.TriConsumer; import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; import software.amazon.smithy.go.codegen.knowledge.GoValidationIndex; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.CollectionShape; 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.ServiceShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.UnionShape; import software.amazon.smithy.model.traits.EnumTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.StringUtils; /** * Generates Go validation middleware and shape helpers. */ public class ValidationGenerator implements GoIntegration { public static final MiddlewareIdentifier OPERATION_INPUT_VALIDATION_MIDDLEWARE_ID = MiddlewareIdentifier .string("OperationInputValidation"); private final List runtimeClientPlugins = new ArrayList<>(); /** * Gets the sort order of the customization from -128 to 127, with lowest * executed first. * * @return Returns the sort order, defaults to 20. */ @Override public byte getOrder() { return 20; } private void execute(GoWriter writer, Model model, SymbolProvider symbolProvider, ServiceShape service) { GoValidationIndex validationIndex = model.getKnowledge(GoValidationIndex.class); Map inputShapeToOperation = new TreeMap<>(); validationIndex.getOperationsRequiringValidation(service).forEach(shapeId -> { OperationShape operationShape = model.expectShape(shapeId).asOperationShape().get(); Shape inputShape = model.expectShape(operationShape.getInput().get()); inputShapeToOperation.put(inputShape, operationShape); }); Set shapesWithHelpers = validationIndex.getShapesRequiringValidationHelpers(service); generateOperationValidationMiddleware(writer, symbolProvider, inputShapeToOperation); generateAddMiddlewareStackHelper(writer, symbolProvider, inputShapeToOperation.values()); generateShapeValidationFunctions(writer, model, symbolProvider, inputShapeToOperation.keySet(), shapesWithHelpers); } private void generateOperationValidationMiddleware( GoWriter writer, SymbolProvider symbolProvider, Map operationShapeMap ) { for (Map.Entry entry : operationShapeMap.entrySet()) { GoStackStepMiddlewareGenerator generator = GoStackStepMiddlewareGenerator.createInitializeStepMiddleware( getOperationValidationMiddlewareName(entry.getValue()), OPERATION_INPUT_VALIDATION_MIDDLEWARE_ID ); String helperName = getShapeValidationFunctionName(entry.getKey(), true); Symbol inputSymbol = symbolProvider.toSymbol(entry.getKey()); generator.writeMiddleware(writer, (g, w) -> { writer.addUseImports(SmithyGoDependency.FMT); // cast input parameters type to the input type of the operation writer.write("input, ok := in.Parameters.($P)", inputSymbol); writer.openBlock("if !ok {", "}", () -> { writer.write("return out, metadata, " + "fmt.Errorf(\"unknown input parameters type %T\", in.Parameters)"); }); writer.openBlock("if err := $L(input); err != nil {", "}", helperName, () -> writer.write("return out, metadata, err")); writer.write("return next.$L(ctx, in)", g.getHandleMethodName()); }); writer.write(""); } } private void generateShapeValidationFunctions( GoWriter writer, Model model, SymbolProvider symbolProvider, Set operationInputShapes, Set shapesWithHelpers ) { GoPointableIndex pointableIndex = GoPointableIndex.of(model); for (ShapeId shapeId : shapesWithHelpers) { Shape shape = model.expectShape(shapeId); boolean topLevelShape = operationInputShapes.contains(shape); String functionName = getShapeValidationFunctionName(shape, topLevelShape); Symbol shapeSymbol = symbolProvider.toSymbol(shape); writer.openBlock("func $L(v $P) error {", "}", functionName, shapeSymbol, () -> { writer.addUseImports(SmithyGoDependency.SMITHY); if (pointableIndex.isNillable(shape)) { writer.openBlock("if v == nil {", "}", () -> writer.write("return nil")); } writer.write("invalidParams := smithy.InvalidParamsError{Context: $S}", shapeSymbol.getName()); switch (shape.getType()) { case STRUCTURE: shape.members().forEach(memberShape -> { if (StreamingTrait.isEventStream(model, memberShape)) { return; } String memberName = symbolProvider.toMemberName(memberShape); Shape targetShape = model.expectShape(memberShape.getTarget()); boolean required = GoValidationIndex.isRequiredParameter(model, memberShape, topLevelShape); boolean hasHelper = shapesWithHelpers.contains(targetShape.getId()); boolean isEnum = targetShape.getTrait(EnumTrait.class).isPresent(); if (required) { Runnable runnable = () -> { writer.write("invalidParams.Add(smithy.NewErrParamRequired($S))", memberName); if (hasHelper) { writer.writeInline("} else "); } else { writer.write("}"); } }; if (isEnum) { writer.write("if len(v.$L) == 0 {", memberName); runnable.run(); } else if (pointableIndex.isNillable(memberShape)) { writer.write("if v.$L == nil {", memberName); runnable.run(); } } if (hasHelper) { Runnable runnable = () -> { String helperName = getShapeValidationFunctionName(targetShape, false); writer.openBlock("if err := $L(v.$L); err != nil {", "}", helperName, memberName, () -> { writer.addUseImports(SmithyGoDependency.SMITHY); writer.write( "invalidParams.AddNested($S, err.(smithy.InvalidParamsError))", memberName); }); }; if (isEnum) { writer.openBlock("if len(v.$L) > 0 {", "}", memberName, runnable); } else if (pointableIndex.isNillable(memberShape)) { writer.openBlock("if v.$L != nil {", "}", memberName, runnable); } } }); break; case LIST: case SET: CollectionShape collectionShape = CodegenUtils.expectCollectionShape(shape); MemberShape member = collectionShape.getMember(); Shape memberTarget = model.expectShape(member.getTarget()); String helperName = getShapeValidationFunctionName(memberTarget, false); writer.openBlock("for i := range v {", "}", () -> { String addr = ""; if (!pointableIndex.isPointable(member) && pointableIndex.isPointable(memberTarget)) { addr = "&"; } writer.openBlock("if err := $L($Lv[i]); err != nil {", "}", helperName, addr, () -> { writer.addUseImports(SmithyGoDependency.SMITHY); writer.write("invalidParams.AddNested(fmt.Sprintf(\"[%d]\", i), " + "err.(smithy.InvalidParamsError))"); }); }); break; case MAP: MapShape mapShape = shape.asMapShape().get(); MemberShape mapValue = mapShape.getValue(); Shape valueTarget = model.expectShape(mapValue.getTarget()); helperName = getShapeValidationFunctionName(valueTarget, false); writer.openBlock("for key := range v {", "}", () -> { String valueVar = "v[key]"; if (!pointableIndex.isPointable(mapValue) && pointableIndex.isPointable(valueTarget)) { writer.write("value := $L", valueVar); valueVar = "&value"; } writer.openBlock("if err := $L($L); err != nil {", "}", helperName, valueVar, () -> { writer.addUseImports(SmithyGoDependency.SMITHY); writer.write("invalidParams.AddNested(fmt.Sprintf(\"[%q]\", key), " + "err.(smithy.InvalidParamsError))"); }); }); break; case UNION: UnionShape unionShape = shape.asUnionShape().get(); Symbol unionSymbol = symbolProvider.toSymbol(unionShape); Set memberShapes = unionShape.getAllMembers().values().stream() .filter(memberShape -> shapesWithHelpers.contains(model.expectShape(memberShape.getTarget()).getId())) .collect(Collectors.toCollection(TreeSet::new)); if (memberShapes.size() > 0) { writer.openBlock("switch uv := v.(type) {", "}", () -> { // Use a TreeSet to sort the members. for (MemberShape unionMember : memberShapes) { Shape target = model.expectShape(unionMember.getTarget()); Symbol memberSymbol = SymbolUtils.createValueSymbolBuilder( symbolProvider.toMemberName(unionMember), unionSymbol.getNamespace() ).build(); String memberHelper = getShapeValidationFunctionName(target, false); writer.openBlock("case *$T:", "", memberSymbol, () -> { String addr = ""; if (!pointableIndex.isPointable(unionMember) && pointableIndex.isPointable(target)) { addr = "&"; } writer.openBlock("if err := $L($Luv.Value); err != nil {", "}", memberHelper, addr, () -> { writer.addUseImports(SmithyGoDependency.SMITHY); writer.write("invalidParams.AddNested(\"[$L]\", " + "err.(smithy.InvalidParamsError))", unionMember.getMemberName()); }); }); } }); } break; default: throw new CodegenException("Unexpected validation helper shape type " + shape.getType()); } writer.write("if invalidParams.Len() > 0 {"); writer.write("return invalidParams"); writer.write("} else {"); writer.write("return nil"); writer.write("}"); }); writer.write(""); } } private static String getOperationValidationMiddlewareName(OperationShape operationShape) { return "validateOp" + StringUtils.capitalize(operationShape.getId().getName()); } private static String getShapeValidationFunctionName(Shape shape, boolean topLevelOpShape) { StringBuilder builder = new StringBuilder(); builder.append("validate"); if (topLevelOpShape) { builder.append("Op"); } builder.append(StringUtils.capitalize(shape.getId().getName())); return builder.toString(); } private String getAddMiddlewareStackHelperFunctionName(OperationShape operationShape) { return "addOp" + StringUtils.capitalize(operationShape.getId().getName()) + "ValidationMiddleware"; } private void generateAddMiddlewareStackHelper( GoWriter writer, SymbolProvider symbolProvider, Collection operationShapes ) { Symbol smithyStack = SymbolUtils.createPointableSymbolBuilder("Stack", SmithyGoDependency.SMITHY_MIDDLEWARE) .build(); for (OperationShape operationShape : operationShapes) { Symbol middlewareSymbol = SymbolUtils.createPointableSymbolBuilder(getOperationValidationMiddlewareName( operationShape)).build(); String functionName = getAddMiddlewareStackHelperFunctionName(operationShape); writer.openBlock("func $L(stack $P) error {", "}", functionName, smithyStack, () -> { writer.write("return stack.Initialize.Add(&$T{}, middleware.After)", middlewareSymbol); }); writer.write(""); } } @Override public void writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, TriConsumer> writerFactory ) { writerFactory.accept("validators.go", settings.getModuleName(), writer -> { execute(writer, model, symbolProvider, settings.getService(model)); }); } @Override public void processFinalizedModel(GoSettings settings, Model model) { GoValidationIndex validationIndex = GoValidationIndex.of(model); ServiceShape service = settings.getService(model); Set requiringValidation = validationIndex.getOperationsRequiringValidation(service); for (ShapeId shapeId : requiringValidation) { OperationShape operationShape = model.expectShape(shapeId).asOperationShape().get(); String helperFunctionName = getAddMiddlewareStackHelperFunctionName(operationShape); runtimeClientPlugins.add(RuntimeClientPlugin.builder() .servicePredicate((m, s) -> s.equals(service)) .operationPredicate((m, s, o) -> o.equals(operationShape)) .registerMiddleware(MiddlewareRegistrar.builder() .resolvedFunction(SymbolUtils.createValueSymbolBuilder(helperFunctionName) .build()) .build()) .build()); } } @Override public List getClientPlugins() { return runtimeClientPlugins; } } Waiters.java000066400000000000000000001247511463735525100363160ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration; import static java.util.Collections.emptySet; import static software.amazon.smithy.go.codegen.GoWriter.autoDocTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.Map; import java.util.Optional; import java.util.Set; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.ClientOptions; import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.ListShape; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; 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.SimpleShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.utils.StringUtils; import software.amazon.smithy.waiters.Acceptor; import software.amazon.smithy.waiters.Matcher; import software.amazon.smithy.waiters.PathComparator; import software.amazon.smithy.waiters.WaitableTrait; import software.amazon.smithy.waiters.Waiter; /** * Implements support for WaitableTrait. */ public class Waiters implements GoIntegration { private static final String WAITER_INVOKER_FUNCTION_NAME = "Wait"; private static final String WAITER_INVOKER_WITH_OUTPUT_FUNCTION_NAME = "WaitForOutput"; public Set getAdditionalClientOptions() { return emptySet(); } @Override public void writeAdditionalFiles(GoCodegenContext ctx) { var service = ctx.settings().getService(ctx.model()); TopDownIndex.of(ctx.model()).getContainedOperations(service).stream() .forEach(operation -> { if (!operation.hasTrait(WaitableTrait.ID)) { return; } Map waiters = operation.expectTrait(WaitableTrait.class).getWaiters(); generateOperationWaiter(ctx, operation, waiters); }); } /** * Generates all waiter components used for the operation. */ private void generateOperationWaiter(GoCodegenContext ctx, OperationShape operation, Map waiters) { var model = ctx.model(); var symbolProvider = ctx.symbolProvider(); var service = ctx.settings().getService(model); ctx.writerDelegator().useShapeWriter(operation, writer -> { waiters.forEach((name, waiter) -> { generateWaiterOptions(model, symbolProvider, writer, operation, name, waiter); generateWaiterClient(model, symbolProvider, writer, operation, name, waiter); generateWaiterInvoker(model, symbolProvider, writer, operation, name, waiter); generateWaiterInvokerWithOutput(model, symbolProvider, writer, operation, name, waiter); generateRetryable(model, symbolProvider, writer, service, operation, name, waiter); }); }); } /** * Generates waiter options to configure a waiter client. */ private void generateWaiterOptions( Model model, SymbolProvider symbolProvider, GoWriter writer, OperationShape operationShape, String waiterName, Waiter waiter ) { String optionsName = generateWaiterOptionsName(waiterName); String waiterClientName = generateWaiterClientName(waiterName); StructureShape inputShape = model.expectShape( operationShape.getInput().get(), StructureShape.class ); StructureShape outputShape = model.expectShape( operationShape.getOutput().get(), StructureShape.class ); Symbol inputSymbol = symbolProvider.toSymbol(inputShape); Symbol outputSymbol = symbolProvider.toSymbol(outputShape); writer.write(""); writer.writeDocs( String.format("%s are waiter options for %s", optionsName, waiterClientName) ); writer.openBlock("type $L struct {", "}", optionsName, () -> { writer.addUseImports(SmithyGoDependency.TIME); writer.write(""); var apiOptionsDocs = autoDocTemplate(""" Set of options to modify how an operation is invoked. These apply to all operations invoked for this client. Use functional options on operation call to modify this list for per operation behavior. Passing options here is functionally equivalent to passing values to this config's ClientOptions field that extend the inner client's APIOptions directly."""); Symbol stackSymbol = SymbolUtils.createPointableSymbolBuilder("Stack", SmithyGoDependency.SMITHY_MIDDLEWARE) .build(); writer.write(goTemplate(""" $W APIOptions []func($P) error """, apiOptionsDocs, stackSymbol)); var clientOptionsDocs = autoDocTemplate(""" Functional options to be passed to all operations invoked by this client. Function values that modify the inner APIOptions are applied after the waiter config's own APIOptions modifiers."""); writer.write(""); writer.write(goTemplate(""" $W ClientOptions []func(*$L) """, clientOptionsDocs, ClientOptions.NAME)); writer.write(""); writer.writeDocs( String.format("MinDelay is the minimum amount of time to delay between retries. " + "If unset, %s will use default minimum delay of %s seconds. " + "Note that MinDelay must resolve to a value lesser than or equal " + "to the MaxDelay.", waiterClientName, waiter.getMinDelay()) ); writer.write("MinDelay time.Duration"); writer.write(""); writer.writeDocs( String.format("MaxDelay is the maximum amount of time to delay between retries. " + "If unset or set to zero, %s will use default max delay of %s seconds. " + "Note that MaxDelay must resolve to value greater than or equal " + "to the MinDelay.", waiterClientName, waiter.getMaxDelay()) ); writer.write("MaxDelay time.Duration"); writer.write(""); writer.writeDocs("LogWaitAttempts is used to enable logging for waiter retry attempts"); writer.write("LogWaitAttempts bool"); writer.write(""); writer.writeDocs( "Retryable is function that can be used to override the " + "service defined waiter-behavior based on operation output, or returned error. " + "This function is used by the waiter to decide if a state is retryable " + "or a terminal state.\n\nBy default service-modeled logic " + "will populate this option. This option can thus be used to define a custom " + "waiter state with fall-back to service-modeled waiter state mutators." + "The function returns an error in case of a failure state. " + "In case of retry state, this function returns a bool value of true and " + "nil error, while in case of success it returns a bool value of false and " + "nil error." ); writer.write( "Retryable func(context.Context, $P, $P, error) " + "(bool, error)", inputSymbol, outputSymbol); } ); writer.write(""); } /** * Generates waiter client used to invoke waiter function. The waiter client is specific to a modeled waiter. * Each waiter client is unique within a enclosure of a service. * This function also generates a waiter client constructor that takes in a API client interface, and waiter options * to configure a waiter client. */ private void generateWaiterClient( Model model, SymbolProvider symbolProvider, GoWriter writer, OperationShape operationShape, String waiterName, Waiter waiter ) { Symbol operationSymbol = symbolProvider.toSymbol(operationShape); String clientName = generateWaiterClientName(waiterName); writer.write(""); writer.writeDocs( String.format("%s defines the waiters for %s", clientName, waiterName) ); writer.openBlock("type $L struct {", "}", clientName, () -> { writer.write(""); writer.write("client $L", OperationInterfaceGenerator.getApiClientInterfaceName(operationSymbol)); writer.write(""); writer.write("options $L", generateWaiterOptionsName(waiterName)); }); writer.write(""); String constructorName = String.format("New%s", clientName); Symbol waiterOptionsSymbol = SymbolUtils.createPointableSymbolBuilder( generateWaiterOptionsName(waiterName) ).build(); Symbol clientSymbol = SymbolUtils.createPointableSymbolBuilder( clientName ).build(); writer.writeDocs( String.format("%s constructs a %s.", constructorName, clientName) ); writer.openBlock("func $L(client $L, optFns ...func($P)) $P {", "}", constructorName, OperationInterfaceGenerator.getApiClientInterfaceName(operationSymbol), waiterOptionsSymbol, clientSymbol, () -> { writer.write("options := $T{}", waiterOptionsSymbol); writer.addUseImports(SmithyGoDependency.TIME); // set defaults writer.write("options.MinDelay = $L * time.Second", waiter.getMinDelay()); writer.write("options.MaxDelay = $L * time.Second", waiter.getMaxDelay()); writer.write("options.Retryable = $L", generateRetryableName(waiterName)); writer.write(""); writer.openBlock("for _, fn := range optFns {", "}", () -> { writer.write("fn(&options)"); }); writer.openBlock("return &$T {", "}", clientSymbol, () -> { writer.write("client: client, "); writer.write("options: options, "); }); }); } /** * Generates waiter invoker functions to call specific operation waiters * These waiter invoker functions is defined on each modeled waiter client. * The invoker function takes in a context, along with operation input, and * optional functional options for the waiter. */ private void generateWaiterInvoker( Model model, SymbolProvider symbolProvider, GoWriter writer, OperationShape operationShape, String waiterName, Waiter waiter ) { StructureShape inputShape = model.expectShape( operationShape.getInput().get(), StructureShape.class ); Symbol inputSymbol = symbolProvider.toSymbol(inputShape); Symbol waiterOptionsSymbol = SymbolUtils.createPointableSymbolBuilder( generateWaiterOptionsName(waiterName) ).build(); Symbol clientSymbol = SymbolUtils.createPointableSymbolBuilder( generateWaiterClientName(waiterName) ).build(); writer.write(""); writer.addUseImports(SmithyGoDependency.CONTEXT); writer.addUseImports(SmithyGoDependency.TIME); writer.writeDocs( String.format( "%s calls the waiter function for %s waiter. The maxWaitDur is the maximum wait duration " + "the waiter will wait. The maxWaitDur is required and must be greater than zero.", WAITER_INVOKER_FUNCTION_NAME, waiterName) ); writer.openBlock( "func (w $P) $L(ctx context.Context, params $P, maxWaitDur time.Duration, optFns ...func($P)) error {", "}", clientSymbol, WAITER_INVOKER_FUNCTION_NAME, inputSymbol, waiterOptionsSymbol, () -> { writer.write( "_, err := w.$L(ctx, params, maxWaitDur, optFns...)", WAITER_INVOKER_WITH_OUTPUT_FUNCTION_NAME ); writer.write("return err"); }); } /** * Generates waiter invoker functions to call specific operation waiters * and return the output of the successful operation. * These waiter invoker functions is defined on each modeled waiter client. * The invoker function takes in a context, along with operation input, and * optional functional options for the waiter. */ private void generateWaiterInvokerWithOutput( Model model, SymbolProvider symbolProvider, GoWriter writer, OperationShape operationShape, String waiterName, Waiter waiter ) { StructureShape inputShape = model.expectShape( operationShape.getInput().get(), StructureShape.class ); StructureShape outputShape = model.expectShape( operationShape.getOutput().get(), StructureShape.class ); Symbol operationSymbol = symbolProvider.toSymbol(operationShape); Symbol inputSymbol = symbolProvider.toSymbol(inputShape); Symbol outputSymbol = symbolProvider.toSymbol(outputShape); Symbol waiterOptionsSymbol = SymbolUtils.createPointableSymbolBuilder( generateWaiterOptionsName(waiterName) ).build(); Symbol clientSymbol = SymbolUtils.createPointableSymbolBuilder( generateWaiterClientName(waiterName) ).build(); writer.write(""); writer.addUseImports(SmithyGoDependency.CONTEXT); writer.addUseImports(SmithyGoDependency.TIME); writer.writeDocs( String.format( "%s calls the waiter function for %s waiter and returns the output of the successful " + "operation. The maxWaitDur is the maximum wait duration the waiter will wait. The " + "maxWaitDur is required and must be greater than zero.", WAITER_INVOKER_WITH_OUTPUT_FUNCTION_NAME, waiterName) ); writer.openBlock( "func (w $P) $L(ctx context.Context, params $P, maxWaitDur time.Duration, optFns ...func($P)) " + "($P, error) {", "}", clientSymbol, WAITER_INVOKER_WITH_OUTPUT_FUNCTION_NAME, inputSymbol, waiterOptionsSymbol, outputSymbol, () -> { writer.openBlock("if maxWaitDur <= 0 {", "}", () -> { writer.addUseImports(SmithyGoDependency.FMT); writer.write( "return nil, fmt.Errorf(\"maximum wait time for waiter must be greater than zero\")" ); }).write(""); writer.write("options := w.options"); writer.openBlock("for _, fn := range optFns {", "}", () -> { writer.write("fn(&options)"); }); writer.write(""); // validate values for MaxDelay from options writer.openBlock("if options.MaxDelay <= 0 {", "}", () -> { writer.write("options.MaxDelay = $L * time.Second", waiter.getMaxDelay()); }); writer.write(""); // validate that MinDelay is lesser than or equal to resolved MaxDelay writer.openBlock("if options.MinDelay > options.MaxDelay {", "}", () -> { writer.addUseImports(SmithyGoDependency.FMT); writer.write("return nil, fmt.Errorf(\"minimum waiter delay %v must be lesser than or equal to " + "maximum waiter delay of %v.\", options.MinDelay, options.MaxDelay)"); }).write(""); writer.addUseImports(SmithyGoDependency.CONTEXT); writer.write("ctx, cancelFn := context.WithTimeout(ctx, maxWaitDur)"); writer.write("defer cancelFn()"); writer.write(""); Symbol loggerMiddleware = SymbolUtils.createValueSymbolBuilder( "Logger", SmithyGoDependency.SMITHY_WAITERS ).build(); writer.write("logger := $T{}", loggerMiddleware); writer.write("remainingTime := maxWaitDur").write(""); writer.write("var attempt int64"); writer.openBlock("for {", "}", () -> { writer.write(""); writer.write("attempt++"); writer.write("apiOptions := options.APIOptions"); writer.write("start := time.Now()").write(""); // add waiter logger middleware to log an attempt, if LogWaitAttempts is enabled. writer.openBlock("if options.LogWaitAttempts {", "}", () -> { writer.write("logger.Attempt = attempt"); writer.write( "apiOptions = append([]func(*middleware.Stack) error{}, options.APIOptions...)"); writer.write("apiOptions = append(apiOptions, logger.AddLogger)"); }).write(""); // make a request var baseOpts = GoWriter.ChainWritable.of( getAdditionalClientOptions().stream() .map(it -> goTemplate("$T,", it)) .toList() ).compose(false); writer.openBlock("out, err := w.client.$T(ctx, params, func (o *Options) { ", "})", operationSymbol, () -> { writer.write(""" baseOpts := []func(*Options) { $W }""", baseOpts); writer.write("o.APIOptions = append(o.APIOptions, apiOptions...)"); writer.write(""" for _, opt := range baseOpts { opt(o) } for _, opt := range options.ClientOptions { opt(o) }"""); }); writer.write(""); // handle response and identify waiter state writer.write("retryable, err := options.Retryable(ctx, params, out, err)"); writer.write("if err != nil { return nil, err }"); writer.write("if !retryable { return out, nil }").write(""); // update remaining time writer.write("remainingTime -= time.Since(start)"); // check if next iteration is possible writer.openBlock("if remainingTime < options.MinDelay || remainingTime <= 0 {", "}", () -> { writer.write("break"); }); writer.write(""); // handle retry delay computation, sleep. Symbol computeDelaySymbol = SymbolUtils.createValueSymbolBuilder( "ComputeDelay", SmithyGoDependency.SMITHY_WAITERS ).build(); writer.writeDocs("compute exponential backoff between waiter retries"); writer.openBlock("delay, err := $T(", ")", computeDelaySymbol, () -> { writer.write("attempt, options.MinDelay, options.MaxDelay, remainingTime,"); }); writer.addUseImports(SmithyGoDependency.FMT); writer.write( "if err != nil { return nil, fmt.Errorf(\"error computing waiter delay, %w\", err)}"); writer.write(""); // update remaining time as per computed delay writer.write("remainingTime -= delay"); // sleep for delay Symbol sleepWithContextSymbol = SymbolUtils.createValueSymbolBuilder( "SleepWithContext", SmithyGoDependency.SMITHY_TIME ).build(); writer.writeDocs("sleep for the delay amount before invoking a request"); writer.openBlock("if err := $T(ctx, delay); err != nil {", "}", sleepWithContextSymbol, () -> { writer.write( "return nil, fmt.Errorf(\"request cancelled while waiting, %w\", err)"); }); }); writer.write("return nil, fmt.Errorf(\"exceeded max wait time for $L waiter\")", waiterName); }); } /** * Generates a waiter state mutator function which is used by the waiter retrier Middleware to mutate * waiter state as per the defined logic and returned operation response. * * @param model the smithy model * @param symbolProvider symbol provider * @param writer the Gowriter * @param serviceShape service shape for which operation waiter is modeled * @param operationShape operation shape on which the waiter is modeled * @param waiterName the waiter name * @param waiter the waiter structure that contains info on modeled waiter */ private void generateRetryable( Model model, SymbolProvider symbolProvider, GoWriter writer, ServiceShape serviceShape, OperationShape operationShape, String waiterName, Waiter waiter ) { StructureShape inputShape = model.expectShape( operationShape.getInput().get(), StructureShape.class ); StructureShape outputShape = model.expectShape( operationShape.getOutput().get(), StructureShape.class ); Symbol inputSymbol = symbolProvider.toSymbol(inputShape); Symbol outputSymbol = symbolProvider.toSymbol(outputShape); writer.write(""); writer.openBlock("func $L(ctx context.Context, input $P, output $P, err error) (bool, error) {", "}", generateRetryableName(waiterName), inputSymbol, outputSymbol, () -> { waiter.getAcceptors().forEach(acceptor -> { writer.write(""); // scope each acceptor to avoid name collisions Matcher matcher = acceptor.getMatcher(); switch (matcher.getMemberName()) { case "output": writer.addUseImports(SmithyGoDependency.GO_JMESPATH); writer.addUseImports(SmithyGoDependency.FMT); Matcher.OutputMember outputMember = (Matcher.OutputMember) matcher; String path = outputMember.getValue().getPath(); String expectedValue = outputMember.getValue().getExpected(); PathComparator comparator = outputMember.getValue().getComparator(); writer.openBlock("if err == nil {", "}", () -> { writer.write("pathValue, err := jmespath.Search($S, output)", path); writer.openBlock("if err != nil {", "}", () -> { writer.write( "return false, " + "fmt.Errorf(\"error evaluating waiter state: %w\", err)"); }).write(""); writer.write("expectedValue := $S", expectedValue); if (comparator == PathComparator.BOOLEAN_EQUALS) { writeWaiterComparator(writer, acceptor, comparator, null, "pathValue", "expectedValue"); } else { String[] pathMembers = path.split("\\."); Shape targetShape = outputShape; for (int i = 0; i < pathMembers.length; i++) { MemberShape member = getComparedMember(model, targetShape, pathMembers[i]); if (member == null) { targetShape = null; break; } targetShape = model.expectShape(member.getTarget()); } if (targetShape == null) { writeWaiterComparator(writer, acceptor, comparator, null, "pathValue", "expectedValue"); } else { Symbol targetSymbol = symbolProvider.toSymbol(targetShape); writeWaiterComparator(writer, acceptor, comparator, targetSymbol, "pathValue", "expectedValue"); } } }); break; case "inputOutput": writer.addUseImports(SmithyGoDependency.GO_JMESPATH); writer.addUseImports(SmithyGoDependency.FMT); Matcher.InputOutputMember ioMember = (Matcher.InputOutputMember) matcher; path = ioMember.getValue().getPath(); expectedValue = ioMember.getValue().getExpected(); comparator = ioMember.getValue().getComparator(); writer.openBlock("if err == nil {", "}", () -> { writer.openBlock("pathValue, err := jmespath.Search($S, &struct{", "})", path, () -> { writer.write("Input $P \n Output $P \n }{", inputSymbol, outputSymbol); writer.write("Input: input, \n Output: output, \n"); }); writer.openBlock("if err != nil {", "}", () -> { writer.write( "return false, " + "fmt.Errorf(\"error evaluating waiter state: %w\", err)"); }); writer.write(""); writer.write("expectedValue := $S", expectedValue); writeWaiterComparator(writer, acceptor, comparator, outputSymbol, "pathValue", "expectedValue"); }); break; case "success": Matcher.SuccessMember successMember = (Matcher.SuccessMember) matcher; writer.openBlock("if err == nil {", "}", () -> { writeMatchedAcceptorReturn(writer, acceptor); }); break; case "errorType": Matcher.ErrorTypeMember errorTypeMember = (Matcher.ErrorTypeMember) matcher; String errorType = errorTypeMember.getValue(); writer.openBlock("if err != nil {", "}", () -> { // identify if this is a modeled error shape Optional errorShapeId = operationShape.getErrors().stream().filter( shapeId -> { return shapeId.getName(serviceShape).equalsIgnoreCase(errorType); }).findFirst(); // if modeled error shape if (errorShapeId.isPresent()) { Shape errorShape = model.expectShape(errorShapeId.get()); Symbol modeledErrorSymbol = symbolProvider.toSymbol(errorShape); writer.addUseImports(SmithyGoDependency.ERRORS); writer.write("var errorType *$T", modeledErrorSymbol); writer.openBlock("if errors.As(err, &errorType) {", "}", () -> { writeMatchedAcceptorReturn(writer, acceptor); }); } else { // fall back to un-modeled error shape matching writer.addUseImports(SmithyGoDependency.SMITHY); writer.addUseImports(SmithyGoDependency.ERRORS); // assert unmodeled error to smithy's API error writer.write("var apiErr smithy.APIError"); writer.write("ok := errors.As(err, &apiErr)"); writer.openBlock("if !ok {", "}", () -> { writer.write("return false, " + "fmt.Errorf(\"expected err to be of type smithy.APIError, " + "got %w\", err)"); }); writer.write(""); writer.openBlock("if $S == apiErr.ErrorCode() {", "}", errorType, () -> { writeMatchedAcceptorReturn(writer, acceptor); }); } }); break; default: throw new CodegenException( String.format("unknown waiter state : %v", matcher.getMemberName()) ); } }); writer.write(""); writer.write("return true, nil"); }); } /** * writes comparators for a given waiter. The comparators are defined within the waiter acceptor. * * @param writer the Gowriter * @param acceptor the waiter acceptor that defines the comparator and acceptor states * @param comparator the comparator * @param targetSymbol the shape symbol of the compared type. * @param actual the variable carrying the actual value obtained. * This may be computed via a jmespath expression or operation response status (success/failure) * @param expected the variable carrying the expected value. This value is as per the modeled waiter. */ private void writeWaiterComparator( GoWriter writer, Acceptor acceptor, PathComparator comparator, Symbol targetSymbol, String actual, String expected ) { if (targetSymbol == null) { targetSymbol = SymbolUtils.createValueSymbolBuilder("string").build(); } String valueAccessor = "string(value)"; Optional isPointable = targetSymbol.getProperty(SymbolUtils.POINTABLE, Boolean.class); if (isPointable.isPresent() && isPointable.get().booleanValue()) { valueAccessor = "string(*value)"; } switch (comparator) { case STRING_EQUALS: writer.write("value, ok := $L.($P)", actual, targetSymbol); writer.write("if !ok {"); writer.write("return false, fmt.Errorf(\"waiter comparator expected $P value, got %T\", $L)}", targetSymbol, actual); writer.write(""); writer.openBlock("if $L == $L {", "}", valueAccessor, expected, () -> { writeMatchedAcceptorReturn(writer, acceptor); }); break; case BOOLEAN_EQUALS: writer.addUseImports(SmithyGoDependency.STRCONV); writer.write("bv, err := strconv.ParseBool($L)", expected); writer.write( "if err != nil { return false, " + "fmt.Errorf(\"error parsing boolean from string %w\", err)}"); writer.write("value, ok := $L.(bool)", actual); writer.openBlock(" if !ok {", "}", () -> { writer.write("return false, " + "fmt.Errorf(\"waiter comparator expected bool value got %T\", $L)", actual); }); writer.write(""); writer.openBlock("if value == bv {", "}", () -> { writeMatchedAcceptorReturn(writer, acceptor); }); break; case ALL_STRING_EQUALS: writer.write("var match = true"); writer.write("listOfValues, ok := $L.([]interface{})", actual); writer.openBlock(" if !ok {", "}", () -> { writer.write("return false, " + "fmt.Errorf(\"waiter comparator expected list got %T\", $L)", actual); }); writer.write(""); writer.write("if len(listOfValues) == 0 { match = false }"); String allStringValueAccessor = valueAccessor; Symbol allStringTargetSymbol = targetSymbol; writer.openBlock("for _, v := range listOfValues {", "}", () -> { writer.write("value, ok := v.($P)", allStringTargetSymbol); writer.write("if !ok {"); writer.write("return false, fmt.Errorf(\"waiter comparator expected $P value, got %T\", $L)}", allStringTargetSymbol, actual); writer.write(""); writer.write("if $L != $L { match = false }", allStringValueAccessor, expected); }); writer.write(""); writer.openBlock("if match {", "}", () -> { writeMatchedAcceptorReturn(writer, acceptor); }); break; case ANY_STRING_EQUALS: writer.write("listOfValues, ok := $L.([]interface{})", actual); writer.openBlock(" if !ok {", "}", () -> { writer.write("return false, " + "fmt.Errorf(\"waiter comparator expected list got %T\", $L)", actual); }); writer.write(""); String anyStringValueAccessor = valueAccessor; Symbol anyStringTargetSymbol = targetSymbol; writer.openBlock("for _, v := range listOfValues {", "}", () -> { writer.write("value, ok := v.($P)", anyStringTargetSymbol); writer.write("if !ok {"); writer.write("return false, fmt.Errorf(\"waiter comparator expected $P value, got %T\", $L)}", anyStringTargetSymbol, actual); writer.write(""); writer.openBlock("if $L == $L {", "}", anyStringValueAccessor, expected, () -> { writeMatchedAcceptorReturn(writer, acceptor); }); }); break; default: throw new CodegenException( String.format("Found unknown waiter path comparator, %s", comparator.toString())); } } /** * Writes return statement for state where a waiter's acceptor state is a match. * * @param writer the Go writer * @param acceptor the waiter acceptor who's state is used to write an appropriate return statement. */ private void writeMatchedAcceptorReturn(GoWriter writer, Acceptor acceptor) { switch (acceptor.getState()) { case SUCCESS: writer.write("return false, nil"); break; case FAILURE: writer.addUseImports(SmithyGoDependency.FMT); writer.write("return false, fmt.Errorf(\"waiter state transitioned to Failure\")"); break; case RETRY: writer.write("return true, nil"); break; default: throw new CodegenException("unknown acceptor state defined for the waiter"); } } private String generateWaiterOptionsName( String waiterName ) { waiterName = StringUtils.capitalize(waiterName); return String.format("%sWaiterOptions", waiterName); } private String generateWaiterClientName( String waiterName ) { waiterName = StringUtils.capitalize(waiterName); return String.format("%sWaiter", waiterName); } private String generateRetryableName( String waiterName ) { waiterName = StringUtils.uncapitalize(waiterName); return String.format("%sStateRetryable", waiterName); } /** * Returns the MemberShape wrt to the provided Shape and name. * For eg, If shape `A` has MemberShape `B`, and the name provided is `B` as string. * We return the MemberShape `B`. * * @param model the generation model. * @param shape the shape that is walked to retreive the shape matching provided name. * @param name name is a single scope path string, and should only match to one or less shapes. * @return MemberShape matching the name. */ private MemberShape getComparedMember(Model model, Shape shape, String name) { name = name.replaceAll("\\[\\]", ""); // if shape is a simple shape, just return shape as member shape if (shape instanceof SimpleShape) { return shape.asMemberShape().get(); } switch (shape.getType()) { case STRUCTURE: StructureShape st = shape.asStructureShape().get(); for (MemberShape memberShape : st.getAllMembers().values()) { if (name.equalsIgnoreCase(memberShape.getMemberName())) { return memberShape; } } break; case LIST: ListShape listShape = shape.asListShape().get(); MemberShape listMember = listShape.getMember(); Shape listTarget = model.expectShape(listMember.getTarget()); return getComparedMember(model, listTarget, name); default: // TODO: add support for * usage with jmespath expression. return null; } // TODO: add support for * usage with jmespath expression. // return null if no shape type matched (this would happen in case of * usage with jmespath expression). return null; } } auth/000077500000000000000000000000001463735525100347645ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integrationAnonymousAuthScheme.java000066400000000000000000000026371463735525100415760ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration.auth; import java.util.List; import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; import software.amazon.smithy.model.traits.synthetic.NoAuthTrait; import software.amazon.smithy.utils.ListUtils; public class AnonymousAuthScheme implements GoIntegration { private static final AuthSchemeDefinition ANONYMOUS_DEFINITION = new AnonymousDefinition(); @Override public List getClientPlugins() { return ListUtils.of( RuntimeClientPlugin.builder() .addAuthSchemeDefinition(NoAuthTrait.ID, ANONYMOUS_DEFINITION) .build() ); } } AnonymousDefinition.java000066400000000000000000000036061463735525100416350ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration.auth; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; /** * Implements codegen for smithy.api#noAuth. */ public class AnonymousDefinition implements AuthSchemeDefinition { @Override public GoWriter.Writable generateServiceOption(ProtocolGenerator.GenerationContext c, ServiceShape s) { return goTemplate("&$T{SchemeID: $T},", SmithyGoTypes.Auth.Option, SmithyGoTypes.Auth.SchemeIDAnonymous); } @Override public GoWriter.Writable generateOperationOption(ProtocolGenerator.GenerationContext c, OperationShape o) { return goTemplate("&$T{SchemeID: $T},", SmithyGoTypes.Auth.Option, SmithyGoTypes.Auth.SchemeIDAnonymous); } @Override public GoWriter.Writable generateOptionsIdentityResolver() { return goTemplate("&$T{}", SmithyGoTypes.Auth.AnonymousIdentityResolver); } } HttpBearerDefinition.java000066400000000000000000000033651463735525100417070ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration.auth; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; /** * Implements codegen for smithy.api#httpBearerAuth. */ public class HttpBearerDefinition implements AuthSchemeDefinition { @Override public GoWriter.Writable generateServiceOption(ProtocolGenerator.GenerationContext context, ServiceShape service) { return goTemplate("&$T{SchemeID: $T},", SmithyGoTypes.Auth.Option, SmithyGoTypes.Auth.SchemeIDHTTPBearer); } @Override public GoWriter.Writable generateOperationOption(ProtocolGenerator.GenerationContext c, OperationShape o) { return goTemplate("&$T{SchemeID: $T},", SmithyGoTypes.Auth.Option, SmithyGoTypes.Auth.SchemeIDHTTPBearer); } } SigV4ADefinition.java000066400000000000000000000075231463735525100407040ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration.auth; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.Map; import software.amazon.smithy.aws.traits.auth.SigV4ATrait; import software.amazon.smithy.aws.traits.auth.SigV4Trait; import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.utils.MapUtils; /** * Implements codegen for aws.auth#sigv4a. */ public class SigV4ADefinition implements AuthSchemeDefinition { private static final Map COMMON_ENV = MapUtils.of( "properties", SmithyGoTypes.Smithy.Properties, "option", SmithyGoTypes.Auth.Option, "schemeId", SmithyGoTypes.Auth.SchemeIDSigV4A, "setSigningName", SmithyGoTypes.Transport.Http.SetSigV4ASigningName, "setSigningRegions", SmithyGoTypes.Transport.Http.SetSigV4ASigningRegions ); @Override public GoWriter.Writable generateServiceOption( ProtocolGenerator.GenerationContext context, ServiceShape service ) { var trait = service.expectTrait(SigV4ATrait.class); return goTemplate(""" &$option:T{ SchemeID: $schemeId:T, SignerProperties: func() $properties:T { var props $properties:T $setSigningName:T(&props, $name:S) $setSigningRegions:T(&props, []string{params.Region}) return props }(), },""", COMMON_ENV, MapUtils.of( "name", trait.getName() )); } @Override public GoWriter.Writable generateOperationOption( ProtocolGenerator.GenerationContext context, OperationShape operation ) { var trait = context.getService().expectTrait(SigV4Trait.class); return goTemplate(""" &$option:T{ SchemeID: $schemeId:T, SignerProperties: func() $properties:T { var props $properties:T $setSigningName:T(&props, $name:S) $setSigningRegions:T(&props, []string{params.Region}) $unsignedPayload:W return props }(), },""", COMMON_ENV, MapUtils.of( "name", trait.getName(), "unsignedPayload", generateIsUnsignedPayload(operation) )); } private GoWriter.Writable generateIsUnsignedPayload(OperationShape operation) { return operation.hasTrait(UnsignedPayloadTrait.class) ? goTemplate("$T(&props, true)", SmithyGoTypes.Transport.Http.SetIsUnsignedPayload) : emptyGoTemplate(); } } SigV4AuthScheme.java000066400000000000000000000036441463735525100405410ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration.auth; import java.util.List; import software.amazon.smithy.aws.traits.auth.SigV4ATrait; import software.amazon.smithy.aws.traits.auth.SigV4Trait; import software.amazon.smithy.go.codegen.auth.AuthParameter; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.utils.ListUtils; /** * Code generation for SigV4/SigV4A. */ public class SigV4AuthScheme implements GoIntegration { private boolean isSigV4XService(Model model, ServiceShape service) { return service.hasTrait(SigV4Trait.class) || service.hasTrait(SigV4ATrait.class); } @Override public List getClientPlugins() { // FUTURE: add default Region client option and scheme definitions - we need a more structured way of // suppressing elements of a GoIntegration before we do so, for now those are registered SDK-side return ListUtils.of( RuntimeClientPlugin.builder() .servicePredicate(this::isSigV4XService) .addAuthParameter(AuthParameter.REGION) .build() ); } } SigV4Definition.java000066400000000000000000000073641463735525100406060ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.integration.auth; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.Map; import software.amazon.smithy.aws.traits.auth.SigV4Trait; import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.utils.MapUtils; /** * Implements codegen for aws.auth#sigv4. */ public class SigV4Definition implements AuthSchemeDefinition { private static final Map COMMON_ENV = MapUtils.of( "properties", SmithyGoTypes.Smithy.Properties, "option", SmithyGoTypes.Auth.Option, "schemeId", SmithyGoTypes.Auth.SchemeIDSigV4, "setSigningName", SmithyGoTypes.Transport.Http.SetSigV4SigningName, "setSigningRegion", SmithyGoTypes.Transport.Http.SetSigV4SigningRegion ); @Override public GoWriter.Writable generateServiceOption(ProtocolGenerator.GenerationContext context, ServiceShape service) { var trait = service.expectTrait(SigV4Trait.class); return goTemplate(""" &$option:T{ SchemeID: $schemeId:T, SignerProperties: func() $properties:T { var props $properties:T $setSigningName:T(&props, $name:S) $setSigningRegion:T(&props, params.Region) return props }(), },""", COMMON_ENV, MapUtils.of( "name", trait.getName() )); } @Override public GoWriter.Writable generateOperationOption( ProtocolGenerator.GenerationContext context, OperationShape operation ) { var trait = context.getService().expectTrait(SigV4Trait.class); return goTemplate(""" &$option:T{ SchemeID: $schemeId:T, SignerProperties: func() $properties:T { var props $properties:T $setSigningName:T(&props, $name:S) $setSigningRegion:T(&props, params.Region) $unsignedPayload:W return props }(), },""", COMMON_ENV, MapUtils.of( "name", trait.getName(), "unsignedPayload", generateIsUnsignedPayload(operation) )); } private GoWriter.Writable generateIsUnsignedPayload(OperationShape operation) { return operation.hasTrait(UnsignedPayloadTrait.class) ? goTemplate("$T(&props, true)", SmithyGoTypes.Transport.Http.SetIsUnsignedPayload) : emptyGoTemplate(); } } knowledge/000077500000000000000000000000001463735525100334575ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegenGoPointableIndex.java000066400000000000000000000227611463735525100375250ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/knowledge/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. * * */ package software.amazon.smithy.go.codegen.knowledge; import java.util.HashSet; import java.util.Set; import java.util.logging.Logger; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.KnowledgeIndex; import software.amazon.smithy.model.knowledge.NeighborProviderIndex; import software.amazon.smithy.model.knowledge.NullableIndex; import software.amazon.smithy.model.neighbor.NeighborProvider; import software.amazon.smithy.model.neighbor.Relationship; import software.amazon.smithy.model.neighbor.RelationshipType; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.shapes.ToShapeId; import software.amazon.smithy.model.traits.EnumTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.SetUtils; /** * An index that checks if a member or shape type should be a pointer type in Go. *

* Extends the rules of smithy's NullableIndex for Go's translation of the smithy shapes to Go types. */ public class GoPointableIndex implements KnowledgeIndex { // we default to this IDL1 checkMode for API stability of the downstream aws-sdk-go-v2 // FUTURE: refactor that downstream codegen to decorate/wrap over this to supply its own checkmode and default to // IDL2 here instead public static final NullableIndex.CheckMode DEFAULT_CHECKMODE = NullableIndex.CheckMode.CLIENT_ZERO_VALUE_V1_NO_INPUT; private static final Logger LOGGER = Logger.getLogger(GoPointableIndex.class.getName()); // All types that are Go value types private static final Set INHERENTLY_VALUE = SetUtils.of( ShapeType.BLOB, ShapeType.LIST, ShapeType.SET, ShapeType.MAP, ShapeType.UNION, ShapeType.DOCUMENT ); // All types that are Go pointer types private static final Set INHERENTLY_POINTABLE = SetUtils.of( ShapeType.BIG_DECIMAL, ShapeType.BIG_INTEGER ); // All types that cannot be dereferenced private static final Set INHERENTLY_NONDEREFERENCABLE = SetUtils.of( // built in slice/map ShapeType.BLOB, ShapeType.LIST, ShapeType.SET, ShapeType.MAP, // Interfaces ShapeType.UNION, ShapeType.DOCUMENT, // known pointer types. ShapeType.BIG_DECIMAL, ShapeType.BIG_INTEGER ); // All types types that are comparable to nil private static final Set INHERENTLY_NILLABLE = SetUtils.of( // built in slice/map ShapeType.BLOB, ShapeType.LIST, ShapeType.SET, ShapeType.MAP, // Interfaces ShapeType.UNION, ShapeType.DOCUMENT, // known pointer types. ShapeType.BIG_DECIMAL, ShapeType.BIG_INTEGER ); private final Model model; private final NullableIndex nullableIndex; private final NullableIndex.CheckMode checkMode; private final Set pointableShapes = new HashSet<>(); private final Set nillableShapes = new HashSet<>(); private final Set dereferencableShapes = new HashSet<>(); public GoPointableIndex(Model model, NullableIndex.CheckMode checkMode) { this.model = model; this.nullableIndex = NullableIndex.of(model); this.checkMode = checkMode; for (Shape shape : model.toSet()) { if (shape.asMemberShape().isPresent()) { MemberShape member = shape.asMemberShape().get(); Shape targetShape = model.expectShape(member.getTarget()); if (isMemberPointable(member, targetShape)) { pointableShapes.add(shape.getId()); } if (isMemberNillable(member, targetShape)) { nillableShapes.add(shape.getId()); } if (isMemberDereferencable(member, targetShape)) { dereferencableShapes.add(shape.getId()); } } else { if (isShapePointable(shape)) { pointableShapes.add(shape.getId()); nillableShapes.add(shape.getId()); } if (isShapeNillable(shape)) { nillableShapes.add(shape.getId()); } if (isShapeDereferencable(shape)) { dereferencableShapes.add(shape.getId()); } } } } public GoPointableIndex(Model model) { this(model, DEFAULT_CHECKMODE); } public static GoPointableIndex of(Model model) { return model.getKnowledge(GoPointableIndex.class, GoPointableIndex::new); } public static GoPointableIndex of(Model model, NullableIndex.CheckMode checkMode) { return model.getKnowledge(GoPointableIndex.class, (model1) -> new GoPointableIndex(model1, checkMode)); } private boolean isMemberDereferencable(MemberShape member, Shape targetShape) { return !INHERENTLY_NONDEREFERENCABLE.contains(targetShape.getType()) && isMemberPointable(member, targetShape); } private boolean isMemberNillable(MemberShape member, Shape targetShape) { return INHERENTLY_NILLABLE.contains(targetShape.getType()) || isMemberPointable(member, targetShape); } private boolean isMemberPointable(MemberShape member, Shape targetShape) { // Streamed blob shapes are never pointers because they are interfaces if (isBlobStream(targetShape)) { return false; } if (INHERENTLY_VALUE.contains(targetShape.getType()) || isShapeEnum(targetShape)) { return false; } return nullableIndex.isMemberNullable(member, checkMode); } private boolean isShapeDereferencable(Shape shape) { return !INHERENTLY_NONDEREFERENCABLE.contains(shape.getType()) && isShapePointable(shape); } private boolean isShapeNillable(Shape shape) { return INHERENTLY_NILLABLE.contains(shape.getType()) || isShapePointable(shape); } private boolean isShapePointable(Shape shape) { // All operation input and output shapes are pointable. if (isOperationStruct(shape)) { return true; } // Streamed blob shapes are never pointers because they are interfaces if (isBlobStream(shape)) { return false; } if (shape.isServiceShape()) { return true; } // This is odd because its not a go type but a function with receiver if (shape.isOperationShape()) { return false; } if (INHERENTLY_POINTABLE.contains(shape.getType())) { return true; } if (INHERENTLY_VALUE.contains(shape.getType()) || isShapeEnum(shape)) { return false; } return nullableIndex.isNullable(shape); } private boolean isShapeEnum(Shape shape) { return shape.getType() == ShapeType.STRING && shape.hasTrait(EnumTrait.class) || shape.getType() == ShapeType.ENUM || shape.getType() == ShapeType.INT_ENUM; } private boolean isBlobStream(Shape shape) { return shape.getType() == ShapeType.BLOB && shape.hasTrait(StreamingTrait.ID); } private boolean isOperationStruct(Shape shape) { NeighborProvider provider = NeighborProviderIndex.of(model).getReverseProvider(); for (Relationship relationship : provider.getNeighbors(shape)) { RelationshipType relationshipType = relationship.getRelationshipType(); if (relationshipType == RelationshipType.INPUT || relationshipType == RelationshipType.OUTPUT) { return true; } } return false; } /** * Returns if the shape should be generated as a Go pointer type or not. * * @param shape the shape to check if should be pointable type. * @return if the shape is should be a Go pointer type. */ public final boolean isPointable(ToShapeId shape) { return pointableShapes.contains(shape.toShapeId()); } /** * Returns if the Go type generated for the shape is comparable to nil. * * @param shape the shape to check * @return if the shape's go type is comparable to nil */ public final boolean isNillable(ToShapeId shape) { return nillableShapes.contains(shape.toShapeId()); } /** * Returns if the Go type generated for the shape can be dereferenced. * * @param shape the shape to check * @return if the shape's go type is dereferencable */ public final boolean isDereferencable(ToShapeId shape) { return dereferencableShapes.contains(shape.toShapeId()); } } GoUsageIndex.java000066400000000000000000000066571463735525100366620ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/knowledge/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.knowledge; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.KnowledgeIndex; import software.amazon.smithy.model.knowledge.OperationIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.neighbor.RelationshipDirection; import software.amazon.smithy.model.neighbor.Walker; 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.shapes.ToShapeId; /** * Provides {@link KnowledgeIndex} of how shapes are used in the model. */ public class GoUsageIndex implements KnowledgeIndex { private final Model model; private final Walker walker; private final Set inputShapes = new HashSet<>(); private final Set outputShapes = new HashSet<>(); public GoUsageIndex(Model model) { this.model = model; this.walker = new Walker(model); TopDownIndex topDownIndex = TopDownIndex.of(model); OperationIndex operationIndex = OperationIndex.of(model); model.shapes(ServiceShape.class).forEach(serviceShape -> { topDownIndex.getContainedOperations(serviceShape).forEach(operationShape -> { StructureShape inputShape = operationIndex.getInput(operationShape).get(); StructureShape outputShape = operationIndex.getOutput(operationShape).get(); inputShapes.addAll(walker.walkShapes(inputShape, relationship -> relationship.getDirection() == RelationshipDirection.DIRECTED).stream() .map(Shape::toShapeId).collect(Collectors.toList())); outputShapes.addAll(walker.walkShapes(outputShape, relationship -> relationship.getDirection() == RelationshipDirection.DIRECTED).stream() .map(Shape::toShapeId).collect(Collectors.toList())); }); }); } /** * Returns whether shape is used as part of an input to an operation. * * @param shape the shape * @return whether the shape is used as input. */ public boolean isUsedForInput(ToShapeId shape) { return inputShapes.contains(shape.toShapeId()); } /** * Returns whether shape is used as output of an operation. * * @param shape the shape * @return whether the shape is used as input. */ public boolean isUsedForOutput(ToShapeId shape) { return outputShapes.contains(shape.toShapeId()); } public static GoUsageIndex of(Model model) { return model.getKnowledge(GoUsageIndex.class, GoUsageIndex::new); } } GoValidationIndex.java000066400000000000000000000214061463735525100376750ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/knowledge/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ /* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.knowledge; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.function.Consumer; import java.util.stream.Collectors; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.KnowledgeIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.neighbor.Walker; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; 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.ToShapeId; import software.amazon.smithy.model.traits.HttpLabelTrait; import software.amazon.smithy.model.traits.RequiredTrait; import software.amazon.smithy.utils.SetUtils; /** * Provides a knowledge index of which service operations and shapes require validation helpers. */ public class GoValidationIndex implements KnowledgeIndex { private final Map> serviceToOperationMap = new HashMap<>(); private final Map> serviceValidationHelpers = new HashMap<>(); public GoValidationIndex(Model model) { TopDownIndex topDownIndex = model.getKnowledge(TopDownIndex.class); Walker walker = new Walker(model); model.shapes(ServiceShape.class).forEach(serviceShape -> { // Go uses unique input shapes per operation so we can index using the input shape as our key Map inputShapeToOperation = new HashMap<>(); Set requireValidationHelpers = new TreeSet<>(); // First pass is to collect member containers that contain members requiring validation Set operations = topDownIndex.getContainedOperations(serviceShape); operations.forEach(operationShape -> { Shape inputShape = model.expectShape(operationShape.getInput().get()); GoValidationIndex.walkValidationTree(walker, inputShape, shape -> { if (shape.isMemberShape()) { Shape container = model.expectShape(((MemberShape) shape).getContainer()); if (isRequiredParameter(model, (MemberShape) shape, inputShape.equals(container))) { inputShapeToOperation.put(inputShape, operationShape); requireValidationHelpers.add(container.toShapeId()); } } }); }); // 2nd step is final all containers that reference the initial containers which require validation until // we've discovered all intermediate containing types inputShapeToOperation.keySet().forEach(input -> { Set helpers = new TreeSet<>(); do { GoValidationIndex.walkValidationTree(walker, input, shape -> { if (shape.isMemberShape()) { MemberShape memberShape = shape.asMemberShape().get(); Shape container = model.expectShape(memberShape.getContainer()); Shape target = model.expectShape(memberShape.getTarget()); if (requireValidationHelpers.contains(target.toShapeId()) && !requireValidationHelpers.contains(container.toShapeId())) { helpers.add(container.toShapeId()); } } }); if (helpers.isEmpty()) { break; } requireValidationHelpers.addAll(helpers); helpers.clear(); } while (true); }); serviceToOperationMap.put(serviceShape.toShapeId(), new TreeSet<>(inputShapeToOperation.values().stream() .map(OperationShape::toShapeId).collect(Collectors.toSet()))); serviceValidationHelpers.put(serviceShape.toShapeId(), requireValidationHelpers); }); } public static GoValidationIndex of(Model model) { return model.getKnowledge(GoValidationIndex.class, GoValidationIndex::new); } /** * Get the set of operations that require validation. * * @param service service to find operations for * @return operations requiring validation */ public Set getOperationsRequiringValidation(ToShapeId service) { return serviceToOperationMap.getOrDefault(service.toShapeId(), SetUtils.of()); } /** * Get whether an operation requires validation. * * @param operation the operation * @return whether the operation requires validation */ public boolean operationRequiresValidation(ToShapeId service, ToShapeId operation) { return getOperationsRequiringValidation(service).contains(operation.toShapeId()); } /** * Get a set of shapes that require validation helpers. * * @param service service to find operations for * @return operations requiring validation */ public Set getShapesRequiringValidationHelpers(ToShapeId service) { return serviceValidationHelpers.getOrDefault(service.toShapeId(), SetUtils.of()); } /** * Returns whether the given shape requires a validation helper. * * @param shape the shape to check * @return whether the shape requires a validation helper */ public boolean isValidationHelperRequired(ToShapeId shape) { return serviceValidationHelpers.containsKey(shape.toShapeId()); } /** * Checks whether a {@link MemberShape} has any validation constraints. * * @param model the model * @param shape the {@link MemberShape} to check * @param validateHttpBindings whether http bindings should be checked for additional implicit constraints * @return whether the {@link MemberShape} has validation costraints */ public static boolean hasValidation(Model model, MemberShape shape, boolean validateHttpBindings) { return isRequiredParameter(model, shape, validateHttpBindings); } /** * Checks whether a {@link MemberShape} is marked as being required explicitly or implicitly. * * @param model the model * @param shape the {@link MemberShape} to check * @param validateHttpBindings whether http bindings should be checked for additional implicit constraints * @return whether the {@link MemberShape} is a required parameter */ public static boolean isRequiredParameter(Model model, MemberShape shape, boolean validateHttpBindings) { Optional requiredTrait = shape.getMemberTrait(model, RequiredTrait.class); return requiredTrait.isPresent() || (validateHttpBindings && shape.getMemberTrait(model, HttpLabelTrait.class).isPresent()); } private static void walkValidationTree(Walker walker, Shape shape, Consumer visitor) { walker.walkShapes(shape, relationship -> { switch (relationship.getRelationshipType()) { case STRUCTURE_MEMBER: case UNION_MEMBER: case MAP_VALUE: case LIST_MEMBER: case SET_MEMBER: case MEMBER_TARGET: return true; default: return false; } }).forEach(visitor::accept); } } smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocol/000077500000000000000000000000001463735525100334205ustar00rootroot00000000000000DeserializeResponseMiddleware.java000066400000000000000000000065321463735525100421670ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocol/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.protocol; import static software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator.createDeserializeStepMiddleware; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.integration.ProtocolGenerator.getDeserializeMiddlewareName; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.integration.ProtocolUtils; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public abstract class DeserializeResponseMiddleware implements GoWriter.Writable { protected final ProtocolGenerator generator; protected final ProtocolGenerator.GenerationContext ctx; protected final OperationShape operation; protected final StructureShape output; public DeserializeResponseMiddleware( ProtocolGenerator generator, ProtocolGenerator.GenerationContext ctx, OperationShape operation ) { this.generator = generator; this.ctx = ctx; this.operation = operation; this.output = ctx.getModel().expectShape(operation.getOutputShape(), StructureShape.class); } @Override public void accept(GoWriter writer) { var middleware = createDeserializeStepMiddleware( getDeserializeMiddlewareName(operation.getId(), ctx.getService(), generator.getProtocolName()), ProtocolUtils.OPERATION_DESERIALIZER_MIDDLEWARE_ID ); writer.write(middleware.asWritable(generateHandleDeserialize(), emptyGoTemplate())); } public abstract GoWriter.Writable generateDeserialize(); private GoWriter.Writable generateHandleDeserialize() { return goTemplate(""" out, metadata, err = next.HandleDeserialize(ctx, in) if err != nil { return out, metadata, err } resp, ok := out.RawResponse.($response:P) if !ok { return out, metadata, $errorf:T("unexpected transport type %T", out.RawResponse) } $deserialize:W return out, metadata, nil """, MapUtils.of( "response", generator.getApplicationProtocol().getResponseType(), "deserialize", generateDeserialize(), "errorf", GoStdlibTypes.Fmt.Errorf )); } } ProtocolUtil.java000066400000000000000000000035741463735525100366540ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocol/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.protocol; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.model.traits.StreamingTrait.isEventStream; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public final class ProtocolUtil { public static final GoWriter.Writable GET_AWS_QUERY_ERROR_CODE = goTemplate(""" func getAwsQueryErrorCode(resp $P) string { header := resp.Header.Get("x-amzn-query-error") if header == "" { return "" } parts := $T(header, ";") if len(parts) != 2 { return "" } return parts[0] } """, SmithyGoDependency.SMITHY_HTTP_TRANSPORT.struct("Response"), SmithyGoDependency.STRINGS.func("Split") ); private ProtocolUtil() {} public static boolean hasEventStream(Model model, Shape shape) { return shape.members().stream() .anyMatch(it -> isEventStream(model, it)); } } SerializeRequestMiddleware.java000066400000000000000000000071771463735525100415160ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocol/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.protocol; import static software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator.createSerializeStepMiddleware; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.integration.ProtocolUtils; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public abstract class SerializeRequestMiddleware implements GoWriter.Writable { protected final ProtocolGenerator generator; protected final ProtocolGenerator.GenerationContext ctx; protected final OperationShape operation; protected final StructureShape input; protected final StructureShape output; public SerializeRequestMiddleware( ProtocolGenerator generator, ProtocolGenerator.GenerationContext ctx, OperationShape operation ) { this.generator = generator; this.ctx = ctx; this.operation = operation; this.input = ctx.getModel().expectShape(operation.getInputShape(), StructureShape.class); this.output = ctx.getModel().expectShape(operation.getOutputShape(), StructureShape.class); } @Override public void accept(GoWriter writer) { var name = ProtocolGenerator.getSerializeMiddlewareName(operation.getId(), ctx.getService(), generator.getProtocolName()); var middleware = createSerializeStepMiddleware(name, ProtocolUtils.OPERATION_SERIALIZER_MIDDLEWARE_ID); writer.write(middleware.asWritable(generateHandleSerialize(), emptyGoTemplate())); } public abstract GoWriter.Writable generateRouteRequest(); public abstract GoWriter.Writable generateSerialize(); private GoWriter.Writable generateHandleSerialize() { return goTemplate(""" input, ok := in.Parameters.($input:P) if !ok { return out, metadata, $errorf:T("unexpected input type %T", in.Parameters) } _ = input req, ok := in.Request.($request:P) if !ok { return out, metadata, $errorf:T("unexpected transport type %T", in.Request) } $route:W $serialize:W return next.HandleSerialize(ctx, in) """, MapUtils.of( "input", ctx.getSymbolProvider().toSymbol(input), "request", generator.getApplicationProtocol().getRequestType(), "route", generateRouteRequest(), "serialize", generateSerialize(), "errorf", GoStdlibTypes.Fmt.Errorf )); } } rpc2/000077500000000000000000000000001463735525100342075ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocolRpc2DeserializeResponseMiddleware.java000066400000000000000000000102421463735525100435550ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocol/rpc2/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.protocol.rpc2; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.protocol.ProtocolUtil.hasEventStream; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.protocol.DeserializeResponseMiddleware; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public abstract class Rpc2DeserializeResponseMiddleware extends DeserializeResponseMiddleware { protected Rpc2DeserializeResponseMiddleware( ProtocolGenerator generator, ProtocolGenerator.GenerationContext ctx, OperationShape operation ) { super(generator, ctx, operation); } protected abstract String getProtocolName(); protected abstract GoWriter.Writable deserializeSuccessResponse(); @Override public GoWriter.Writable generateDeserialize() { return goTemplate(""" if resp.Header.Get("smithy-protocol") != $protocol:S { return out, metadata, &$deserError:T{ Err: $errorf:T( "unexpected smithy-protocol response header '%s' (HTTP status: %s)", resp.Header.Get("smithy-protocol"), resp.Status, ), } } if resp.StatusCode != 200 { return out, metadata, $deserializeError:L(resp) } $handleResponse:W """, MapUtils.of( "deserError", SmithyGoDependency.SMITHY.struct("DeserializationError"), "protocol", getProtocolName(), "errorf", GoStdlibTypes.Fmt.Errorf, "handleResponse", handleResponse(), "deserializeError", ProtocolGenerator .getOperationErrorDeserFunctionName(operation, ctx.getService(), "rpc2") )); } private GoWriter.Writable handleResponse() { if (output.members().isEmpty()) { return discardDeserialize(); } else if (hasEventStream(ctx.getModel(), output)) { return deserializeEventStream(); } return deserializeSuccessResponse(); } private GoWriter.Writable discardDeserialize() { return goTemplate(""" if _, err = $copy:T($discard:T, resp.Body); err != nil { return out, metadata, $errorf:T("discard response body: %w", err) } out.Result = &$result:T{} """, MapUtils.of( "copy", GoStdlibTypes.Io.Copy, "discard", GoStdlibTypes.Io.IoUtil.Discard, "errorf", GoStdlibTypes.Fmt.Errorf, "result", ctx.getSymbolProvider().toSymbol(output) )); } // Basically a no-op. Event stream deserializer middleware, implemented elsewhere, will handle the wire-up here, // including handling the initial-response message to deserialize any non-stream members to output. private GoWriter.Writable deserializeEventStream() { return goTemplate("out.Result = &$T{}", ctx.getSymbolProvider().toSymbol(output)); } } Rpc2ProtocolGenerator.java000066400000000000000000000052741463735525100412610ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocol/rpc2/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.protocol.rpc2; import static software.amazon.smithy.go.codegen.ApplicationProtocol.createDefaultHttpApplicationProtocol; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.go.codegen.ApplicationProtocol; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.protocol.DeserializeResponseMiddleware; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public abstract class Rpc2ProtocolGenerator implements ProtocolGenerator { public static final String SMITHY_PROTOCOL_NAME = "rpc-v2-cbor"; public static final String CONTENT_TYPE = "application/cbor"; public abstract Rpc2SerializeRequestMiddleware getSerializeRequestMiddleware( ProtocolGenerator generator, ProtocolGenerator.GenerationContext ctx, OperationShape operation ); public abstract DeserializeResponseMiddleware getDeserializeResponseMiddleware( ProtocolGenerator generator, ProtocolGenerator.GenerationContext ctx, OperationShape operation ); @Override public final ApplicationProtocol getApplicationProtocol() { return createDefaultHttpApplicationProtocol(); } @Override public final void generateRequestSerializers(GenerationContext ctx) { TopDownIndex.of(ctx.getModel()).getContainedOperations(ctx.getService()).forEach(it -> { ctx.getWriter().get().write(getSerializeRequestMiddleware(this, ctx, it)); }); } @Override public final void generateResponseDeserializers(GenerationContext ctx) { TopDownIndex.of(ctx.getModel()).getContainedOperations(ctx.getService()).forEach(it -> { ctx.getWriter().get().write(getDeserializeResponseMiddleware(this, ctx, it)); }); } @Override public void generateEventStreamComponents(GenerationContext context) { throw new CodegenException("event stream codegen is not currently supported in smithy-go"); } } Rpc2SerializeRequestMiddleware.java000066400000000000000000000070121463735525100430770ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocol/rpc2/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.protocol.rpc2; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.EventStreamGenerator; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.protocol.SerializeRequestMiddleware; import software.amazon.smithy.go.codegen.trait.BackfilledInputOutputTrait; import software.amazon.smithy.model.knowledge.EventStreamIndex; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public abstract class Rpc2SerializeRequestMiddleware extends SerializeRequestMiddleware { private final EventStreamIndex eventStreamIndex; protected Rpc2SerializeRequestMiddleware( ProtocolGenerator generator, ProtocolGenerator.GenerationContext ctx, OperationShape operation ) { super(generator, ctx, operation); this.eventStreamIndex = EventStreamIndex.of(ctx.getModel()); } public abstract String getProtocolName(); public abstract String getContentType(); @Override public final GoWriter.Writable generateRouteRequest() { return goTemplate(""" req.Method = $methodPost:T req.URL.Path = "/service/$service:L/operation/$operation:L" req.Header.Set("smithy-protocol", $protocol:S) $contentTypeHeader:W $acceptHeader:W """, MapUtils.of( "methodPost", GoStdlibTypes.Net.Http.MethodPost, "service", ctx.getService().getId().getName(), "operation", operation.getId().getName(), "protocol", getProtocolName(), "contentTypeHeader", setContentTypeHeader(), "acceptHeader", acceptHeader() )); } private GoWriter.Writable setContentTypeHeader() { if (input.hasTrait(BackfilledInputOutputTrait.class)) { return emptyGoTemplate(); } return goTemplate(""" req.Header.Set("Content-Type", $S) """, isInputEventStream() ? EventStreamGenerator.AMZ_CONTENT_TYPE : getContentType()); } private GoWriter.Writable acceptHeader() { return goTemplate(""" req.Header.Set("Accept", $S) """, isOutputEventStream() ? EventStreamGenerator.AMZ_CONTENT_TYPE : getContentType()); } private boolean isInputEventStream() { return eventStreamIndex.getInputInfo(operation).isPresent(); } private boolean isOutputEventStream() { return eventStreamIndex.getOutputInfo(operation).isPresent(); } } cbor/000077500000000000000000000000001463735525100351345ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocol/rpc2DeserializeMiddleware.java000066400000000000000000000056211463735525100422410ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocol/rpc2/cbor/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.protocol.rpc2.cbor; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.protocol.rpc2.Rpc2ProtocolGenerator.SMITHY_PROTOCOL_NAME; import static software.amazon.smithy.go.codegen.serde.cbor.CborDeserializerGenerator.getDeserializerName; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.protocol.rpc2.Rpc2DeserializeResponseMiddleware; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.utils.MapUtils; final class DeserializeMiddleware extends Rpc2DeserializeResponseMiddleware { DeserializeMiddleware( ProtocolGenerator generator, ProtocolGenerator.GenerationContext ctx, OperationShape operation ) { super(generator, ctx, operation); } @Override protected String getProtocolName() { return SMITHY_PROTOCOL_NAME; } @Override public GoWriter.Writable deserializeSuccessResponse() { return goTemplate(""" payload, err := $readAll:T(resp.Body) if err != nil { return out, metadata, err } if len(payload) == 0 { out.Result = &$output:T{} return out, metadata, nil } cv, err := $decode:T(payload) if err != nil { return out, metadata, err } output, err := $deserialize:L(cv) if err != nil { return out, metadata, err } out.Result = output """, MapUtils.of( "readAll", GoStdlibTypes.Io.ReadAll, "decode", SmithyGoTypes.Encoding.Cbor.Decode, "deserialize", getDeserializerName(output), "output", ctx.getSymbolProvider() .toSymbol(ctx.getModel().expectShape(operation.getOutputShape())) )); } } ProtocolUtil.java000066400000000000000000000044521463735525100404430ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocol/rpc2/cbor/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.protocol.rpc2.cbor; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.utils.MapUtils; final class ProtocolUtil { public static final GoWriter.Writable GET_PROTOCOL_ERROR_INFO = goTemplate(""" func getProtocolErrorInfo(payload []byte) (typ, msg string, v $cborValue:T, err error) { v, err = $cborDecode:T(payload) if err != nil { return "", "", nil, $fmtErrorf:T("decode: %w", err) } mv, ok := v.($cborMap:T) if !ok { return "", "", nil, $fmtErrorf:T("unexpected payload type %T", v) } if ctyp, ok := mv["__type"]; ok { if ttyp, ok := ctyp.($cborString:T); ok { typ = string(ttyp) } } if cmsg, ok := mv["message"]; ok { if tmsg, ok := cmsg.($cborString:T); ok { msg = string(tmsg) } } return typ, msg, mv, nil } """, MapUtils.of( "fmtErrorf", GoStdlibTypes.Fmt.Errorf, "cborDecode", SmithyGoTypes.Encoding.Cbor.Decode, "cborValue", SmithyGoTypes.Encoding.Cbor.Value, "cborMap", SmithyGoTypes.Encoding.Cbor.Map, "cborString", SmithyGoTypes.Encoding.Cbor.String )); private ProtocolUtil() {} } Rpc2CborProtocolGenerator.java000066400000000000000000000227711463735525100430150ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocol/rpc2/cbor/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.protocol.rpc2.cbor; import static java.util.stream.Collectors.toCollection; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.protocol.ProtocolUtil.GET_AWS_QUERY_ERROR_CODE; import static software.amazon.smithy.go.codegen.protocol.rpc2.cbor.ProtocolUtil.GET_PROTOCOL_ERROR_INFO; import static software.amazon.smithy.go.codegen.serde.SerdeUtil.getShapesToSerde; import static software.amazon.smithy.go.codegen.serde.cbor.CborDeserializerGenerator.getDeserializerName; import java.util.LinkedHashSet; import java.util.stream.Stream; import software.amazon.smithy.aws.traits.protocols.AwsQueryCompatibleTrait; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.protocol.DeserializeResponseMiddleware; import software.amazon.smithy.go.codegen.protocol.rpc2.Rpc2ProtocolGenerator; import software.amazon.smithy.go.codegen.protocol.rpc2.Rpc2SerializeRequestMiddleware; import software.amazon.smithy.go.codegen.serde.cbor.CborDeserializerGenerator; import software.amazon.smithy.go.codegen.serde.cbor.CborSerializerGenerator; import software.amazon.smithy.model.knowledge.TopDownIndex; 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.protocol.traits.Rpcv2CborTrait; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public class Rpc2CborProtocolGenerator extends Rpc2ProtocolGenerator { @Override public final ShapeId getProtocol() { return Rpcv2CborTrait.ID; } @Override public void generateSharedSerializerComponents(GenerationContext context) { var model = context.getModel(); var service = context.getService(); var shapes = TopDownIndex.of(model).getContainedOperations(service).stream() .map(it -> model.expectShape(it.getInputShape(), StructureShape.class)) .flatMap(it -> getShapesToSerde(model, it).stream()) .sorted() .collect(toCollection(LinkedHashSet::new)); var generator = new CborSerializerGenerator(context); context.getWriter().get().write(generator.generate(shapes)); } @Override public void generateSharedDeserializerComponents(GenerationContext context) { var model = context.getModel(); var service = context.getService(); var operations = TopDownIndex.of(model).getContainedOperations(service); var outputShapes = operations.stream() .map(it -> model.expectShape(it.getOutputShape(), StructureShape.class)) .filter(it -> !it.members().isEmpty()) .flatMap(it -> getShapesToSerde(model, it).stream()); var errorShapes = operations.stream() .flatMap(it -> it.getErrors().stream()) .map(model::expectShape) .flatMap(it -> getShapesToSerde(model, it).stream()); var generator = new CborDeserializerGenerator(context); var writer = context.getWriter().get(); writer.write(generator.generate( Stream.concat(outputShapes, errorShapes) .sorted() .collect(toCollection(LinkedHashSet::new))) // in case of overlap ); writer.write(GoWriter.ChainWritable.of( operations.stream() .sorted() .map(it -> deserializeOperationError(context, it)) .toList() ).compose()); writer.write(GET_PROTOCOL_ERROR_INFO); writer.write(GET_AWS_QUERY_ERROR_CODE); } @Override public final Rpc2SerializeRequestMiddleware getSerializeRequestMiddleware( ProtocolGenerator generator, GenerationContext ctx, OperationShape operation ) { return new SerializeMiddleware(generator, ctx, operation); } @Override public final DeserializeResponseMiddleware getDeserializeResponseMiddleware( ProtocolGenerator generator, GenerationContext ctx, OperationShape operation ) { return new DeserializeMiddleware(generator, ctx, operation); } private GoWriter.Writable deserializeOperationError( ProtocolGenerator.GenerationContext ctx, OperationShape operation ) { var model = ctx.getModel(); var service = ctx.getService(); return goTemplate(""" func $func:L(resp $smithyhttpResponse:P) error { payload, err := $readAll:T(resp.Body) if err != nil { return &$deserError:T{Err: $fmtErrorf:T("read response body: %w", err)} } typ, msg, v, err := getProtocolErrorInfo(payload) if err != nil { return &$deserError:T{Err: $fmtErrorf:T("get error info: %w", err)} } if len(typ) == 0 { typ = "UnknownError" } if len(msg) == 0 { msg = "UnknownError" } _ = v switch string(typ) { $errors:W default: $awsQueryCompatible:W return &$genericAPIError:T{Code: typ, Message: msg} } } """, MapUtils.of( "cborDecode", SmithyGoTypes.Encoding.Cbor.Decode, "cborMap", SmithyGoTypes.Encoding.Cbor.Map, "cborString", SmithyGoTypes.Encoding.Cbor.String ), MapUtils.of( "deserError", SmithyGoDependency.SMITHY.pointableSymbol("DeserializationError"), "fmtErrorf", GoStdlibTypes.Fmt.Errorf, "func", ProtocolGenerator.getOperationErrorDeserFunctionName(operation, service, "rpc2"), "genericAPIError", SmithyGoDependency.SMITHY.pointableSymbol("GenericAPIError"), "readAll", SmithyGoDependency.IO.func("ReadAll"), "smithyhttpResponse", SmithyGoTypes.Transport.Http.Response, "awsQueryCompatible", ctx.getService().hasTrait(AwsQueryCompatibleTrait.class) ? deserializeAwsQueryError() : emptyGoTemplate(), "errors", GoWriter.ChainWritable.of( operation.getErrors(service).stream() .map(it -> deserializeErrorCase(ctx, model.expectShape(it, StructureShape.class))) .toList() ).compose(false) )); } private GoWriter.Writable deserializeErrorCase(GenerationContext ctx, StructureShape error) { return goTemplate(""" case $type:S: verr, err := $deserialize:L(v) if err != nil { return &$deserError:T{ Err: $fmtErrorf:T("deserialize $type:L: %w", err), Snapshot: payload, } } $awsQueryCompatible:W return verr """, MapUtils.of( "deserError", SmithyGoDependency.SMITHY.pointableSymbol("DeserializationError"), "deserialize", getDeserializerName(error), "equalFold", SmithyGoDependency.STRINGS.func("EqualFold"), "fmtErrorf", GoStdlibTypes.Fmt.Errorf, "type", error.getId().toString(), "awsQueryCompatible", ctx.getService().hasTrait(AwsQueryCompatibleTrait.class) ? deserializeModeledAwsQueryError() : emptyGoTemplate() )); } private GoWriter.Writable deserializeAwsQueryError() { return goTemplate(""" if qtype := getAwsQueryErrorCode(resp); len(qt) > 0 { typ = qtype }"""); } private GoWriter.Writable deserializeModeledAwsQueryError() { return goTemplate(""" if qtype := getAwsQueryErrorCode(resp); len(qt) > 0 { verr.ErrorCodeOverride = $T(qtype) }""", SmithyGoTypes.Ptr.String); } } SerializeMiddleware.java000066400000000000000000000056601463735525100417330ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/protocol/rpc2/cbor/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.protocol.rpc2.cbor; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.protocol.rpc2.Rpc2ProtocolGenerator.CONTENT_TYPE; import static software.amazon.smithy.go.codegen.protocol.rpc2.Rpc2ProtocolGenerator.SMITHY_PROTOCOL_NAME; import static software.amazon.smithy.go.codegen.serde.cbor.CborSerializerGenerator.getSerializerName; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.protocol.rpc2.Rpc2SerializeRequestMiddleware; import software.amazon.smithy.go.codegen.trait.BackfilledInputOutputTrait; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.utils.MapUtils; final class SerializeMiddleware extends Rpc2SerializeRequestMiddleware { SerializeMiddleware( ProtocolGenerator generator, ProtocolGenerator.GenerationContext ctx, OperationShape operation ) { super(generator, ctx, operation); } @Override public String getProtocolName() { return SMITHY_PROTOCOL_NAME; } @Override public String getContentType() { return CONTENT_TYPE; } @Override public GoWriter.Writable generateSerialize() { if (input.hasTrait(BackfilledInputOutputTrait.class)) { return emptyGoTemplate(); } return goTemplate(""" cv, err := $serialize:L(input) if err != nil { return out, metadata, &$error:T{Err: err} } payload := $reader:T($encode:T(cv)) if req, err = req.SetStream(payload); err != nil { return out, metadata, &$error:T{Err: err} } in.Request = req """, MapUtils.of( "serialize", getSerializerName(input), "encode", SmithyGoTypes.Encoding.Cbor.Encode, "reader", GoStdlibTypes.Bytes.NewReader, "error", SmithyGoTypes.Smithy.SerializationError )); } } requestcompression/000077500000000000000000000000001463735525100354525ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegenRequestCompression.java000066400000000000000000000162071463735525100421750ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/requestcompression/* * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.requestcompression; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import java.util.ArrayList; import java.util.List; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoCodegenPlugin; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoUniverseTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.go.codegen.integration.ConfigField; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.go.codegen.integration.MiddlewareRegistrar; import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.traits.RequestCompressionTrait; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.MapUtils; public final class RequestCompression implements GoIntegration { private static final String DISABLE_REQUEST_COMPRESSION = "DisableRequestCompression"; private static final String REQUEST_MIN_COMPRESSION_SIZE_BYTES = "RequestMinCompressSizeBytes"; private final List runtimeClientPlugins = new ArrayList<>(); // Write operation plugin for request compression middleware @Override public void processFinalizedModel(GoSettings settings, Model model) { ServiceShape service = settings.getService(model); TopDownIndex.of(model) .getContainedOperations(service).forEach(operation -> { if (!operation.hasTrait(RequestCompressionTrait.class)) { return; } SymbolProvider symbolProvider = GoCodegenPlugin.createSymbolProvider(model, settings); String funcName = getAddRequestCompressionMiddlewareFuncName( symbolProvider.toSymbol(operation).getName() ); runtimeClientPlugins.add(RuntimeClientPlugin.builder().operationPredicate((m, s, o) -> { if (!o.hasTrait(RequestCompressionTrait.class)) { return false; } return o.equals(operation); }).registerMiddleware(MiddlewareRegistrar.builder() .resolvedFunction(SymbolUtils.buildPackageSymbol(funcName)) .useClientOptions().build()) .build()); }); } @Override public void writeAdditionalFiles( GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator goDelegator ) { ServiceShape service = settings.getService(model); TopDownIndex.of(model).getContainedOperations(service).forEach(operation -> { if (!operation.hasTrait(RequestCompressionTrait.class)) { return; } goDelegator.useShapeWriter(operation, writeMiddlewareHelper(symbolProvider, operation)); }); } public static boolean isRequestCompressionService(Model model, ServiceShape service) { return TopDownIndex.of(model) .getContainedOperations(service).stream() .anyMatch(it -> it.hasTrait(RequestCompressionTrait.class)); } @Override public List getClientPlugins() { runtimeClientPlugins.add( RuntimeClientPlugin.builder() .servicePredicate(RequestCompression::isRequestCompressionService) .configFields(ListUtils.of( ConfigField.builder() .name(DISABLE_REQUEST_COMPRESSION) .type(GoUniverseTypes.Bool) .documentation( "Whether to disable automatic request compression for supported operations.") .build(), ConfigField.builder() .name(REQUEST_MIN_COMPRESSION_SIZE_BYTES) .type(GoUniverseTypes.Int64) .documentation("The minimum request body size, in bytes, at which compression " + "should occur. The default value is 10 KiB. Values must fall within " + "[0, 1MiB].") .build() )) .build() ); return runtimeClientPlugins; } private GoWriter.Writable generateAlgorithmList(List algorithms) { return goTemplate(""" []string{ $W } """, GoWriter.ChainWritable.of( algorithms.stream() .map(it -> goTemplate("$S,", it)) .toList() ).compose(false)); } private static String getAddRequestCompressionMiddlewareFuncName(String operationName) { return String.format("addOperation%sRequestCompressionMiddleware", operationName); } private GoWriter.Writable writeMiddlewareHelper(SymbolProvider symbolProvider, OperationShape operation) { String operationName = symbolProvider.toSymbol(operation).getName(); RequestCompressionTrait trait = operation.expectTrait(RequestCompressionTrait.class); return goTemplate(""" func $add:L(stack $stack:P, options Options) error { return $addInternal:T(stack, options.DisableRequestCompression, options.RequestMinCompressSizeBytes, $algorithms:W) } """, MapUtils.of( "add", getAddRequestCompressionMiddlewareFuncName(operationName), "stack", SmithyGoTypes.Middleware.Stack, "addInternal", SmithyGoTypes.Private.RequestCompression.AddRequestCompression, "algorithms", generateAlgorithmList(trait.getEncodings()) )); } } smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde/000077500000000000000000000000001463735525100326615ustar00rootroot00000000000000SerdeUtil.java000066400000000000000000000107011463735525100353440ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.serde; import static java.util.stream.Collectors.toSet; import java.util.HashSet; import java.util.Set; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.BlobShape; import software.amazon.smithy.model.shapes.BooleanShape; import software.amazon.smithy.model.shapes.ByteShape; import software.amazon.smithy.model.shapes.DoubleShape; import software.amazon.smithy.model.shapes.FloatShape; import software.amazon.smithy.model.shapes.IntegerShape; import software.amazon.smithy.model.shapes.LongShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.ShortShape; import software.amazon.smithy.model.shapes.StringShape; import software.amazon.smithy.model.shapes.TimestampShape; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public final class SerdeUtil { private SerdeUtil() {} /** * Gets the set of all shapes that require serde codegen for the given root shape. This is generally called for * every input/output shape in a model, with all the results collected into a single set. * @param model The model * @param shape The root shape to walk to find serdeables. * @return The set of shapes that require serde codegen. */ public static Set getShapesToSerde(Model model, Shape shape) { var toSerde = new HashSet(); visitShapesToSerde(model, shape, toSerde); // We don't want to actually generate serde for event stream unions - their variants can target errors, which // shouldn't be handled generally. We DO want any of their inner members though which is why we didn't filter // them in the previous visit step. // // Serde for the root unions is handled as a special case by event streaming serde codegen. return toSerde.stream() .filter(it -> !it.hasTrait(StreamingTrait.class)) .collect(toSet()); } /** * Normalizes a scalar shape, erasing any nullability information and giving the shape a single unique synthetic ID. * Non-scalar shapes are returned unmodified. * @param shape The shape. * @return The normalized shape. */ public static Shape normalize(Shape shape) { return switch (shape.getType()) { case BLOB -> BlobShape.builder().id("com.amazonaws.synthetic#Blob").build(); case BOOLEAN -> BooleanShape.builder().id("com.amazonaws.synthetic#Bool").build(); case STRING -> StringShape.builder().id("com.amazonaws.synthetic#String").build(); case TIMESTAMP -> TimestampShape.builder().id("com.amazonaws.synthetic#Time").build(); case BYTE -> ByteShape.builder().id("com.amazonaws.synthetic#Int8").build(); case SHORT -> ShortShape.builder().id("com.amazonaws.synthetic#Int16").build(); case INTEGER -> IntegerShape.builder().id("com.amazonaws.synthetic#Int32").build(); case LONG -> LongShape.builder().id("com.amazonaws.synthetic#Int64").build(); case FLOAT -> FloatShape.builder().id("com.amazonaws.synthetic#Float32").build(); case DOUBLE -> DoubleShape.builder().id("com.amazonaws.synthetic#Float64").build(); default -> shape; }; } private static void visitShapesToSerde(Model model, Shape shape, Set visited) { if (isUnit(shape.getId()) || visited.contains(shape)) { return; } visited.add(normalize(shape)); shape.members().stream() .map(it -> model.expectShape(it.getTarget())) .forEach(it -> visitShapesToSerde(model, it, visited)); } private static boolean isUnit(ShapeId id) { return id.toString().equals("smithy.api#Unit"); } } cbor/000077500000000000000000000000001463735525100335275ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serdeCborDeserializerGenerator.java000066400000000000000000000442571463735525100415050ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde/cbor/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.serde.cbor; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.SymbolUtils.buildSymbol; import static software.amazon.smithy.go.codegen.SymbolUtils.getReference; import static software.amazon.smithy.go.codegen.SymbolUtils.isNilable; import static software.amazon.smithy.go.codegen.SymbolUtils.isPointable; import static software.amazon.smithy.go.codegen.serde.SerdeUtil.normalize; import java.util.Set; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.ProtocolDocumentGenerator; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.CollectionShape; import software.amazon.smithy.model.shapes.MapShape; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.UnionShape; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public final class CborDeserializerGenerator { private final Model model; private final SymbolProvider symbolProvider; private final GoSettings settings; public CborDeserializerGenerator(ProtocolGenerator.GenerationContext ctx) { this.model = ctx.getModel(); this.symbolProvider = ctx.getSymbolProvider(); this.settings = ctx.getSettings(); } public static String getDeserializerName(Shape shape) { return "deserializeCBOR_" + shape.getId().getName(); } public GoWriter.Writable generate(Set shapes) { return GoWriter.ChainWritable.of( shapes.stream() .map(this::deserializeShape) .toList() ).compose(); } private GoWriter.Writable deserializeShape(Shape shape) { return switch (shape.getType()) { case BIG_INTEGER, BIG_DECIMAL -> throw new CodegenException("arbitrary-precision nums are not supported (" + shape.getType() + ")"); case BYTE -> deserializeStatic(shape, SmithyGoTypes.Encoding.Cbor.AsInt8); // special types with coercers case SHORT -> deserializeStatic(shape, SmithyGoTypes.Encoding.Cbor.AsInt16); case INTEGER -> deserializeStatic(shape, SmithyGoTypes.Encoding.Cbor.AsInt32); case LONG -> deserializeStatic(shape, SmithyGoTypes.Encoding.Cbor.AsInt64); case FLOAT -> deserializeStatic(shape, SmithyGoTypes.Encoding.Cbor.AsFloat32); case DOUBLE -> deserializeStatic(shape, SmithyGoTypes.Encoding.Cbor.AsFloat64); case TIMESTAMP -> deserializeStatic(shape, SmithyGoTypes.Encoding.Cbor.AsTime); case INT_ENUM -> deserializeIntEnum(shape); case STRING -> deserializeString(shape); case DOCUMENT -> deserializeDocument(shape); // implemented, but not currently supported default -> deserializeAssertFunc(shape); // everything else is a static assert }; } private GoWriter.Writable deserializeStatic(Shape shape, Symbol coercer) { return goTemplate(""" func $deserName:L(v $cborValue:T) ($type:T, error) { return $coercer:T(v) } """, MapUtils.of( "deserName", getDeserializerName(shape), "cborValue", SmithyGoTypes.Encoding.Cbor.Value, "type", symbolProvider.toSymbol(shape), "coercer", coercer )); } private GoWriter.Writable deserializeIntEnum(Shape shape) { return goTemplate(""" func $name:L(v $cborValue:T) ($shapeType:T, error) { av, err := $asInt32:T(v) if err != nil { return 0, err } return $shapeType:T(av), nil } """, MapUtils.of( "name", getDeserializerName(shape), "cborValue", SmithyGoTypes.Encoding.Cbor.Value, "shapeType", symbolProvider.toSymbol(shape), "asInt32", SmithyGoTypes.Encoding.Cbor.AsInt32 )); } private GoWriter.Writable deserializeString(Shape shape) { return goTemplate(""" func $name:L(v $cborValue:T) (string, error) { av, ok := v.($assert:T) if !ok { return "", $error:T("unexpected value type %T", v) } return string(av), nil } """, MapUtils.of( "name", getDeserializerName(shape), "cborValue", SmithyGoTypes.Encoding.Cbor.Value, "assert", SmithyGoTypes.Encoding.Cbor.String, "error", GoStdlibTypes.Fmt.Errorf )); } private GoWriter.Writable deserializeAssertFunc(Shape shape) { return goTemplate(""" func $name:L(v $cborValue:T) ($shapeType:P, error) { av, ok := v.($assert:W) if !ok { return $zero:W, $error:T("unexpected value type %T", v) } $deserialize:W } """, MapUtils.of( "name", getDeserializerName(shape), "cborValue", SmithyGoTypes.Encoding.Cbor.Value, "shapeType", symbolProvider.toSymbol(shape), "assert", typeAssert(shape), "zero", zeroValue(shape), "error", GoStdlibTypes.Fmt.Errorf, "deserialize", deserializeAsserted(shape, "av") )); } private GoWriter.Writable typeAssert(Shape shape) { return switch (shape.getType()) { case STRING, ENUM -> goTemplate("$T", SmithyGoTypes.Encoding.Cbor.String); case BLOB -> goTemplate("$T", SmithyGoTypes.Encoding.Cbor.Slice); case LIST, SET -> goTemplate("$T", SmithyGoTypes.Encoding.Cbor.List); case MAP, STRUCTURE, UNION -> goTemplate("$T", SmithyGoTypes.Encoding.Cbor.Map); case TIMESTAMP, BIG_DECIMAL, BIG_INTEGER -> goTemplate("$P", SmithyGoTypes.Encoding.Cbor.Tag); case BOOLEAN -> goTemplate("$T", SmithyGoTypes.Encoding.Cbor.Bool); default -> throw new CodegenException("Unexpected shape for single-assert: " + shape.getType()); }; } private GoWriter.Writable zeroValue(Shape shape) { return switch (shape.getType()) { case STRING -> goTemplate("\"\""); case BOOLEAN -> goTemplate("false"); case BLOB, LIST, SET, MAP, STRUCTURE, UNION -> goTemplate("nil"); case ENUM -> goTemplate("$T(\"\")", symbolProvider.toSymbol(shape)); case INT_ENUM -> goTemplate("$T(0)", symbolProvider.toSymbol(shape)); default -> throw new CodegenException("Unexpected shape for zero-value: " + shape.getType()); }; } private GoWriter.Writable deserializeAsserted(Shape shape, String ident) { return switch (shape.getType()) { case STRING -> goTemplate("return string($L), nil", ident); case ENUM -> goTemplate("return $T($L), nil", symbolProvider.toSymbol(shape), ident); case BOOLEAN -> goTemplate("return bool($L), nil", ident); case BLOB -> goTemplate("return []byte($L), nil", ident); case LIST, SET -> deserializeList((CollectionShape) shape, ident); case MAP -> deserializeMap((MapShape) shape, ident); case STRUCTURE -> deserializeStruct((StructureShape) shape, ident); case UNION -> deserializeUnion((UnionShape) shape, ident); default -> throw new CodegenException("Cannot deserialize " + shape.getType()); }; } private GoWriter.Writable deserializeList(CollectionShape shape, String ident) { var target = normalize(model.expectShape(shape.getMember().getTarget())); var symbol = symbolProvider.toSymbol(shape); var targetSymbol = symbolProvider.toSymbol(target); return goTemplate(""" var dl $type:T for _, si := range $ident:L { $sparse:W di, err := $deserialize:L(si) if err != nil { return nil, err } dl = append(dl, $deref:L di) } return dl, nil """, MapUtils.of( "type", symbol, "ident", ident, "deserialize", getDeserializerName(target), "deref", resolveDeref(getReference(symbol), targetSymbol), "sparse", isNilable(getReference(symbol)) ? handleSparseList() : emptyGoTemplate() )); } private GoWriter.Writable handleSparseList() { return goTemplate(""" if _, ok := si.($P); ok { dl = append(dl, nil) continue } """, SmithyGoTypes.Encoding.Cbor.Nil); } private GoWriter.Writable deserializeMap(MapShape shape, String ident) { var value = normalize(model.expectShape(shape.getValue().getTarget())); var symbol = symbolProvider.toSymbol(shape); var valueSymbol = symbolProvider.toSymbol(value); return goTemplate(""" dm := $type:T{} for key, sv := range $ident:L { $sparse:W dv, err := $deserialize:L(sv) if err != nil { return nil, err } dm[key] = $deref:L dv } return dm, nil """, MapUtils.of( "type", symbol, "ident", ident, "deserialize", getDeserializerName(value), "deref", resolveDeref(getReference(symbol), valueSymbol), "sparse", isNilable(getReference(symbol)) ? handleSparseMap() : emptyGoTemplate() )); } private GoWriter.Writable handleSparseMap() { return goTemplate(""" if _, ok := sv.($P); ok { dm[key] = nil continue } """, SmithyGoTypes.Encoding.Cbor.Nil); } private String resolveDeref(Symbol ref, Symbol deserialized) { if (isPointable(ref) == isPointable(deserialized)) { return ""; } return isPointable(deserialized) ? "*" : "&"; } private GoWriter.Writable deserializeStruct(StructureShape shape, String ident) { return goTemplate(""" ds := &$type:T{} for key, sv := range $ident:L { _, _ = key, sv $fields:W } return ds, nil """, MapUtils.of( "type", symbolProvider.toSymbol(shape), "ident", ident, "fields", GoWriter.ChainWritable.of( shape.getAllMembers().values().stream() .map(this::deserializeField) .toList() ).compose() )); } private GoWriter.Writable deserializeField(MemberShape member) { var target = model.expectShape(member.getTarget()); if (target.hasTrait(StreamingTrait.class)) { return emptyGoTemplate(); // event stream, not an actual field } var memberSymbol = symbolProvider.toSymbol(member); return goTemplate(""" if key == $field:S { $nilable:W dv, err := $deserialize:L(sv) if err != nil { return nil, err } ds.$fieldName:L = $deref:W } """, MapUtils.of( "field", member.getMemberName(), "fieldName", symbolProvider.toMemberName(member), "deserialize", getDeserializerName(normalize(target)), "deref", generateStructFieldDeref(member, "dv"), "nilable", isNilable(memberSymbol) ? handleSparseField() : emptyGoTemplate() )); } private GoWriter.Writable handleSparseField() { return goTemplate(""" if _, ok := sv.($P); ok { continue }""", SmithyGoTypes.Encoding.Cbor.Nil); } private GoWriter.Writable generateStructFieldDeref(MemberShape member, String ident) { var symbol = symbolProvider.toSymbol(member); if (!isPointable(symbol)) { return goTemplate(ident); } return switch (model.expectShape(member.getTarget()).getType()) { case BYTE -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Int8, ident); case SHORT -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Int16, ident); case INTEGER -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Int32, ident); case LONG -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Int64, ident); case FLOAT -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Float32, ident); case DOUBLE -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Float64, ident); case STRING -> goTemplate("$T($L)", SmithyGoTypes.Ptr.String, ident); case BOOLEAN -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Bool, ident); case TIMESTAMP -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Time, ident); default -> goTemplate(ident); }; } private GoWriter.Writable deserializeUnion(UnionShape union, String ident) { return goTemplate(""" for key, sv := range $ident:L { $variants:W } return nil, $errorf:T("unrecognized variant") """, MapUtils.of( "type", symbolProvider.toSymbol(union), "ident", ident, "errorf", GoStdlibTypes.Fmt.Errorf, "variants", GoWriter.ChainWritable.of( union.getAllMembers().values().stream() .map(it -> deserializeVariant(union, it, "sv")) .toList() ).compose() )); } private GoWriter.Writable deserializeVariant(UnionShape union, MemberShape member, String ident) { var target = normalize(model.expectShape(member.getTarget())); var symbol = symbolProvider.toSymbol(union); var variantSymbol = buildSymbol(symbolProvider.toMemberName(member), symbol.getNamespace()); return goTemplate(""" if key == $variantName:S { if _, ok := $ident:L.($cborNil:P); ok { continue } dv, err := $deserialize:L($ident:L) if err != nil { return nil, err } return &$variantSymbol:T{Value: $deref:L dv}, nil } """, MapUtils.of( "cborNil", SmithyGoDependency.SMITHY_CBOR.struct("Nil"), "variantName", member.getMemberName(), "deserialize", getDeserializerName(target), "ident", ident, "variantSymbol", variantSymbol, "deref", target.getType() == ShapeType.STRUCTURE ? "*" : "" )); } private GoWriter.Writable deserializeDocument(Shape shape) { var unmarshaler = ProtocolDocumentGenerator.Utilities.getInternalDocumentSymbolBuilder(settings, ProtocolDocumentGenerator.INTERNAL_NEW_DOCUMENT_UNMARSHALER_FUNC).build(); return goTemplate(""" func $deser:L(v $cborValue:T) ($document:T, error) { return $unmarshaler:T(v), nil } """, MapUtils.of( "deser", getDeserializerName(shape), "cborValue", SmithyGoTypes.Encoding.Cbor.Value, "document", symbolProvider.toSymbol(shape), "unmarshaler", unmarshaler )); } } CborSerializerGenerator.java000066400000000000000000000331041463735525100411610ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serde/cbor/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.serde.cbor; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.SymbolUtils.buildSymbol; import static software.amazon.smithy.go.codegen.SymbolUtils.getReference; import static software.amazon.smithy.go.codegen.SymbolUtils.isNilable; import static software.amazon.smithy.go.codegen.SymbolUtils.isPointable; import static software.amazon.smithy.go.codegen.serde.SerdeUtil.normalize; import java.util.Set; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.CollectionShape; import software.amazon.smithy.model.shapes.DocumentShape; import software.amazon.smithy.model.shapes.MapShape; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.TimestampShape; import software.amazon.smithy.model.shapes.UnionShape; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public final class CborSerializerGenerator { private final Model model; private final SymbolProvider symbolProvider; public CborSerializerGenerator(ProtocolGenerator.GenerationContext ctx) { this.model = ctx.getModel(); this.symbolProvider = ctx.getSymbolProvider(); } public static String getSerializerName(Shape shape) { return "serializeCBOR_" + shape.getId().getName(); } public GoWriter.Writable generate(Set shapes) { return GoWriter.ChainWritable.of( shapes.stream() .map(this::generateShapeSerializer) .toList() ).compose(); } private GoWriter.Writable generateShapeSerializer(Shape shape) { return goTemplate(""" func $name:L(v $shapeType:P) ($cborValue:T, error) { $serialize:W } """, MapUtils.of( "name", getSerializerName(shape), "shapeType", symbolProvider.toSymbol(shape), "cborValue", SmithyGoTypes.Encoding.Cbor.Value, "serialize", generateSerializeValue(shape) )); } private GoWriter.Writable generateSerializeValue(Shape shape) { return switch (shape.getType()) { case BYTE, SHORT, INTEGER, LONG, INT_ENUM -> generateSerializeIntegral(); case FLOAT -> goTemplate("return $T(v), nil", SmithyGoTypes.Encoding.Cbor.Float32); case DOUBLE -> goTemplate("return $T(v), nil", SmithyGoTypes.Encoding.Cbor.Float64); case STRING -> goTemplate("return $T(v), nil", SmithyGoTypes.Encoding.Cbor.String); case BOOLEAN -> goTemplate("return $T(v), nil", SmithyGoTypes.Encoding.Cbor.Bool); case BLOB -> goTemplate("return $T(v), nil", SmithyGoTypes.Encoding.Cbor.Slice); case ENUM -> goTemplate("return $T(string(v)), nil", SmithyGoTypes.Encoding.Cbor.String); case TIMESTAMP -> generateSerializeTimestamp((TimestampShape) shape); case LIST, SET -> generateSerializeList((CollectionShape) shape); case MAP -> generateSerializeMap((MapShape) shape); case STRUCTURE -> generateSerializeStruct((StructureShape) shape); case UNION -> generateSerializeUnion((UnionShape) shape); case DOCUMENT -> serializeDocument((DocumentShape) shape); // implemented, but not currently supported case BIG_INTEGER, BIG_DECIMAL -> throw new CodegenException("arbitrary-precision nums are not supported (" + shape.getType() + ")"); case MEMBER, SERVICE, RESOURCE, OPERATION -> throw new CodegenException("cannot generate serializer for shape type " + shape.getType()); }; } private GoWriter.Writable generateSerializeIntegral() { return goTemplate(""" if v < 0 { return $T(uint64(-v)), nil } return $T(uint64(v)), nil """, SmithyGoTypes.Encoding.Cbor.NegInt, SmithyGoTypes.Encoding.Cbor.Uint); } private GoWriter.Writable generateSerializeTimestamp(TimestampShape shape) { return goTemplate(""" return &$tag:T{ ID: 1, Value: $float64:T(float64(v.UnixMilli()) / 1000), }, nil """, MapUtils.of( "tag", SmithyGoTypes.Encoding.Cbor.Tag, "float64", SmithyGoTypes.Encoding.Cbor.Float64 )); } private GoWriter.Writable generateSerializeList(CollectionShape shape) { var target = normalize(model.expectShape(shape.getMember().getTarget())); var symbol = symbolProvider.toSymbol(shape); var targetSymbol = symbolProvider.toSymbol(target); return goTemplate(""" vl := $list:T{} for i := range v { $sparse:W ser, err := $serialize:L($indirect:L v[i]) if err != nil { return nil, err } vl = append(vl, ser) } return vl, nil """, MapUtils.of( "list", SmithyGoTypes.Encoding.Cbor.List, "sparse", isNilable(getReference(symbol)) ? handleSparseList() : emptyGoTemplate(), "serialize", getSerializerName(target), "indirect", resolveIndirect(getReference(symbol), targetSymbol) )); } private GoWriter.Writable handleSparseList() { return goTemplate(""" if v[i] == nil { vl = append(vl, &$T{}) continue } """, SmithyGoTypes.Encoding.Cbor.Nil); } private GoWriter.Writable generateSerializeMap(MapShape shape) { var value = normalize(model.expectShape(shape.getValue().getTarget())); var symbol = symbolProvider.toSymbol(shape); var valueSymbol = symbolProvider.toSymbol(value); return goTemplate(""" vm := $map:T{} for k, vv := range v { $sparse:W ser, err := $serialize:L($indirect:L vv) if err != nil { return nil, err } vm[k] = ser } return vm, nil """, MapUtils.of( "map", SmithyGoTypes.Encoding.Cbor.Map, "sparse", isNilable(getReference(symbol)) ? handleSparseMap() : emptyGoTemplate(), "serialize", getSerializerName(value), "indirect", resolveIndirect(getReference(symbol), valueSymbol) )); } private GoWriter.Writable handleSparseMap() { return goTemplate(""" if vv == nil { vm[k] = &$T{} continue } """, SmithyGoTypes.Encoding.Cbor.Nil); } private GoWriter.Writable generateSerializeStruct(StructureShape shape) { return goTemplate(""" vm := $map:T{} $serialize:W return vm, nil """, MapUtils.of( "map", SmithyGoTypes.Encoding.Cbor.Map, "serialize", GoWriter.ChainWritable.of( shape.getAllMembers().values().stream() .map(this::generateSerializeField) .toList() ).compose(false) )); } private GoWriter.Writable generateSerializeField(MemberShape member) { var target = normalize(model.expectShape(member.getTarget())); if (target.hasTrait(StreamingTrait.class)) { return emptyGoTemplate(); // event stream, not an actual field } var symbol = symbolProvider.toSymbol(member); return switch (target.getType()) { case BYTE, SHORT, INTEGER, LONG, FLOAT, DOUBLE, STRING, BOOLEAN, TIMESTAMP -> isPointable(symbol) ? serializeNilableMember(member, target, true) : serializeMember(member, target); case BLOB, LIST, SET, MAP, STRUCTURE, UNION -> serializeNilableMember(member, target, false); default -> serializeMember(member, target); }; } private GoWriter.Writable serializeNilableMember(MemberShape member, Shape target, boolean deref) { return goTemplate(""" if v.$field:L != nil { ser, err := $serialize:L($deref:L v.$field:L) if err != nil { return nil, err } vm[$key:S] = ser } """, MapUtils.of( "field", symbolProvider.toMemberName(member), "key", member.getMemberName(), "serialize", getSerializerName(target), "deref", deref ? "*" : "" )); } private GoWriter.Writable serializeMember(MemberShape member, Shape target) { return goTemplate(""" ser$key:L, err := $serialize:L(v.$field:L) if err != nil { return nil, err } vm[$key:S] = ser$key:L """, MapUtils.of( "field", symbolProvider.toMemberName(member), "key", member.getMemberName(), "serialize", getSerializerName(target) )); } private GoWriter.Writable generateSerializeUnion(UnionShape union) { return goTemplate(""" vm := $map:T{} switch uv := v.(type) { $serialize:W default: return nil, $errorf:T("unknown variant type %T", v) } return vm, nil """, MapUtils.of( "map", SmithyGoTypes.Encoding.Cbor.Map, "errorf", GoStdlibTypes.Fmt.Errorf, "serialize", GoWriter.ChainWritable.of( union.getAllMembers().values().stream() .map(it -> serializeVariant(union, it)) .toList() ).compose(false) )); } private GoWriter.Writable serializeVariant(UnionShape union, MemberShape member) { var target = normalize(model.expectShape(member.getTarget())); var symbol = symbolProvider.toSymbol(union); var variantSymbol = buildSymbol(symbolProvider.toMemberName(member), symbol.getNamespace()); return goTemplate(""" case *$variant:T: ser, err := $serialize:L($indirect:L uv.Value) if err != nil { return nil, err } vm[$key:S] = ser """, MapUtils.of( "variant", variantSymbol, "serialize", getSerializerName(target), "key", member.getMemberName(), "indirect", target.getType() == ShapeType.STRUCTURE ? "&" : "" )); } private GoWriter.Writable serializeDocument(DocumentShape document) { return goTemplate(""" raw, err := v.MarshalSmithyDocument() if err != nil { return nil, err } return $encodeRaw:T(raw), nil """, MapUtils.of( "encoder", SmithyGoTypes.Document.Cbor.NewEncoder, "encodeRaw", SmithyGoTypes.Encoding.Cbor.EncodeRaw )); } private String resolveIndirect(Symbol ref, Symbol serialized) { if (isPointable(ref) == isPointable(serialized)) { return ""; } return isPointable(serialized) ? "&" : "*"; } } smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/000077500000000000000000000000001463735525100330655ustar00rootroot00000000000000NoopServiceStruct.java000066400000000000000000000075401463735525100373200ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.OperationIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; /** * Generates a no-op implementation of the service that returns 501 Not Implemented for every operation. */ @SmithyInternalApi public final class NoopServiceStruct implements GoWriter.Writable { public static final String NAME = "NoopFallbackService"; private final Model model; private final ServiceShape service; private final SymbolProvider symbolProvider; private final OperationIndex operationIndex; public NoopServiceStruct(Model model, ServiceShape service, SymbolProvider symbolProvider) { this.model = model; this.service = service; this.symbolProvider = symbolProvider; this.operationIndex = OperationIndex.of(model); } @Override public void accept(GoWriter writer) { writer.write(generateStruct()); } private GoWriter.Writable generateStruct() { return goTemplate(""" type $struct:L struct{} var _ $interface:L = (*$struct:L)(nil) $operations:W """, MapUtils.of( "struct", NAME, "interface", ServerInterface.NAME, "operations", generateOperations() )); } private GoWriter.Writable generateOperations() { return GoWriter.ChainWritable.of( TopDownIndex.of(model).getContainedOperations(service).stream() .filter(op -> !ServerCodegenUtil.operationHasEventStream( model, operationIndex.expectInputShape(op), operationIndex.expectOutputShape(op))) .map(this::generateOperation) .toList() ).compose(); } private GoWriter.Writable generateOperation(OperationShape operation) { final var operationSymbol = symbolProvider.toSymbol(operation); return goTemplate(""" func (*$struct:L) $operation:L($context:T, $input:P) ($output:P, error) { return nil, &$notImplemented:L{$operationName:S} } """, MapUtils.of( "struct", NAME, "operation", operationSymbol.getName(), "context", GoStdlibTypes.Context.Context, "input", symbolProvider.toSymbol(model.expectShape(operation.getInputShape())), "output", symbolProvider.toSymbol(model.expectShape(operation.getOutputShape())), "notImplemented", NotImplementedError.NAME, "operationName", operationSymbol.getName() ) ); } } NotImplementedError.java000066400000000000000000000034521463735525100376130ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; /** * Generates the NotImplemented error sentinel to be returned when a service doesn't support a specific action. */ @SmithyInternalApi public final class NotImplementedError implements GoWriter.Writable { public static final String NAME = "NotImplemented"; @Override public void accept(GoWriter writer) { writer.write(generateStruct()); } private GoWriter.Writable generateStruct() { return goTemplate(""" type $struct:L struct { Operation string } var _ error = (*$struct:L)(nil) func (err *$struct:L) Error() string { return $sprintf:T("%s is not implemented", err.Operation) } """, MapUtils.of( "struct", NAME, "sprintf", GoStdlibTypes.Fmt.Sprintf )); } } OptionsStruct.java000066400000000000000000000031471463735525100365160ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public final class OptionsStruct implements GoWriter.Writable { public static final String NAME = "Options"; private final ServerProtocolGenerator protocolGenerator; public OptionsStruct(ServerProtocolGenerator protocolGenerator) { this.protocolGenerator = protocolGenerator; } @Override public void accept(GoWriter writer) { writer.write(generate()); } private GoWriter.Writable generate() { return goTemplate(""" type $this:L struct { $protocolOptions:W } """, MapUtils.of( "this", NAME, "protocolOptions", protocolGenerator.generateOptions() )); } } RequestHandler.java000066400000000000000000000054731463735525100366100ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; /** * Generates a concrete request handler. This class handles base struct generation, options, and construction, the * protocol generator must fill in the actual handler logic as appropriate. */ @SmithyInternalApi public final class RequestHandler implements GoWriter.Writable { public static final String NAME = "RequestHandler"; private final ServerProtocolGenerator protocolGenerator; public RequestHandler(ServerProtocolGenerator protocolGenerator) { this.protocolGenerator = protocolGenerator; } @Override public void accept(GoWriter writer) { writer.write(generate()); } private GoWriter.Writable generate() { return GoWriter.ChainWritable.of( generateStruct(), generateNew(), protocolGenerator.generateHandleRequest() ).compose(); } private GoWriter.Writable generateStruct() { return goTemplate(""" type $this:L struct { service $service:L options $options:L } """, MapUtils.of( "this", NAME, "service", ServerInterface.NAME, "options", OptionsStruct.NAME )); } private GoWriter.Writable generateNew() { return goTemplate(""" func New(svc $interface:L, opts $options:L, optFns ...func(*$options:L)) *$this:L { o := opts for _, fn := range optFns { fn(&o) } h := &$this:L{ service: svc, options: o, } return h } """, MapUtils.of( "this", NAME, "interface", ServerInterface.NAME, "options", OptionsStruct.NAME )); } } ServerCodegenPlugin.java000066400000000000000000000056551463735525100375760ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server; import java.util.logging.Logger; import software.amazon.smithy.build.PluginContext; import software.amazon.smithy.build.SmithyBuildPlugin; import software.amazon.smithy.codegen.core.directed.CodegenDirector; import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.utils.SmithyInternalApi; /** * Plugin to trigger Go server code generation. */ @SmithyInternalApi public final class ServerCodegenPlugin implements SmithyBuildPlugin { private static final Logger LOGGER = Logger.getLogger(ServerCodegenPlugin.class.getName()); @Override public String getName() { return "go-server-codegen"; } @Override public void execute(PluginContext context) { String onlyBuild = System.getenv("SMITHY_GO_BUILD_API"); if (onlyBuild != null && !onlyBuild.isEmpty()) { String targetServiceId = GoSettings.from(context.getSettings(), GoSettings.ArtifactType.SERVER).getService().toString(); boolean found = false; for (String includeServiceId : onlyBuild.split(",")) { if (targetServiceId.startsWith(includeServiceId)) { found = true; break; } } if (!found) { LOGGER.info("skipping " + targetServiceId); return; } } generate(context); } private void generate(PluginContext context) { CodegenDirector runner = new CodegenDirector<>(); runner.model(context.getModel()); runner.directedCodegen(new ServerDirectedCodegen()); runner.integrationClass(GoIntegration.class); runner.fileManifest(context.getFileManifest()); GoSettings settings = runner.settings(GoSettings.class, context.getSettings()); runner.service(settings.getService()); runner.performDefaultCodegenTransforms(); runner.createDedicatedInputsAndOutputs(); runner.changeStringEnumsToEnumShapes(false); runner.run(); } } ServerCodegenUtil.java000066400000000000000000000101251463735525100372410ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server; import static java.util.stream.Collectors.toSet; import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.BlobShape; import software.amazon.smithy.model.shapes.BooleanShape; import software.amazon.smithy.model.shapes.ByteShape; import software.amazon.smithy.model.shapes.DoubleShape; import software.amazon.smithy.model.shapes.FloatShape; import software.amazon.smithy.model.shapes.IntegerShape; import software.amazon.smithy.model.shapes.LongShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.ShortShape; import software.amazon.smithy.model.shapes.StringShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.TimestampShape; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.model.traits.UnitTypeTrait; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public final class ServerCodegenUtil { private ServerCodegenUtil() {} public static boolean operationHasEventStream( Model model, StructureShape inputShape, StructureShape outputShape ) { return Stream .concat( inputShape.members().stream(), outputShape.members().stream()) .anyMatch(memberShape -> StreamingTrait.isEventStream(model, memberShape)); } public static Set getShapesToSerde(Model model, Shape shape) { if (isUnit(shape.getId())) { return new HashSet<>(); } return Stream.concat( Stream.of(normalize(shape)), shape.members().stream() .map(it -> model.expectShape(it.getTarget())) .flatMap(it -> getShapesToSerde(model, it).stream()) ).collect(toSet()); } public static Shape normalize(Shape shape) { return switch (shape.getType()) { case BLOB -> BlobShape.builder().id("com.amazonaws.synthetic#Blob").build(); case BOOLEAN -> BooleanShape.builder().id("com.amazonaws.synthetic#Bool").build(); case STRING -> StringShape.builder().id("com.amazonaws.synthetic#String").build(); case TIMESTAMP -> TimestampShape.builder().id("com.amazonaws.synthetic#Time").build(); case BYTE -> ByteShape.builder().id("com.amazonaws.synthetic#Int8").build(); case SHORT -> ShortShape.builder().id("com.amazonaws.synthetic#Int16").build(); case INTEGER -> IntegerShape.builder().id("com.amazonaws.synthetic#Int32").build(); case LONG -> LongShape.builder().id("com.amazonaws.synthetic#Int64").build(); case FLOAT -> FloatShape.builder().id("com.amazonaws.synthetic#Float32").build(); case DOUBLE -> DoubleShape.builder().id("com.amazonaws.synthetic#Float64").build(); default -> shape; }; } public static boolean isUnit(ShapeId id) { return id.toString().equals("smithy.api#Unit"); } public static Model withUnit(Model model) { return model.toBuilder() .addShape( StructureShape.builder() .id("smithy.api#Unit") .addTrait(new UnitTypeTrait()) .build() ) .build(); } } ServerDirectedCodegen.java000066400000000000000000000244111463735525100400520ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server; import static java.util.stream.Collectors.toSet; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.server.ServerCodegenUtil.getShapesToSerde; import static software.amazon.smithy.go.codegen.server.ServerCodegenUtil.isUnit; import static software.amazon.smithy.go.codegen.server.ServerCodegenUtil.withUnit; import java.util.List; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.SymbolDependency; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.codegen.core.WriterDelegator; import software.amazon.smithy.codegen.core.directed.CreateContextDirective; import software.amazon.smithy.codegen.core.directed.CreateSymbolProviderDirective; import software.amazon.smithy.codegen.core.directed.DirectedCodegen; import software.amazon.smithy.codegen.core.directed.GenerateEnumDirective; import software.amazon.smithy.codegen.core.directed.GenerateErrorDirective; import software.amazon.smithy.codegen.core.directed.GenerateIntEnumDirective; import software.amazon.smithy.codegen.core.directed.GenerateOperationDirective; import software.amazon.smithy.codegen.core.directed.GenerateServiceDirective; import software.amazon.smithy.codegen.core.directed.GenerateStructureDirective; import software.amazon.smithy.codegen.core.directed.GenerateUnionDirective; import software.amazon.smithy.go.codegen.CodegenUtils; import software.amazon.smithy.go.codegen.EnumGenerator; import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoModGenerator; import software.amazon.smithy.go.codegen.GoModuleInfo; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.IntEnumGenerator; import software.amazon.smithy.go.codegen.ManifestWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.StructureGenerator; import software.amazon.smithy.go.codegen.SymbolVisitor; import software.amazon.smithy.go.codegen.UnionGenerator; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.EnumShape; import software.amazon.smithy.model.shapes.IntEnumShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public class ServerDirectedCodegen implements DirectedCodegen { @Override public SymbolProvider createSymbolProvider(CreateSymbolProviderDirective directive) { return new SymbolVisitor(withUnit(directive.model()), directive.settings()); } @Override public GoCodegenContext createContext(CreateContextDirective directive) { return new GoCodegenContext( withUnit(directive.model()), directive.settings(), directive.symbolProvider(), directive.fileManifest(), new WriterDelegator<>(directive.fileManifest(), directive.symbolProvider(), (filename, namespace) -> new GoWriter(namespace)), directive.integrations() ); } @Override public void generateService(GenerateServiceDirective directive) { var namespace = directive.settings().getModuleName(); var delegator = directive.context().writerDelegator(); var settings = directive.settings(); var protocolGenerator = resolveProtocolGenerator(directive.context()); var model = directive.model(); var service = directive.service(); var shapesToDeserialize = TopDownIndex.of(model).getContainedOperations(service).stream() .map(it -> model.expectShape(it.getInputShape(), StructureShape.class)) .flatMap(it -> getShapesToSerde(model, it).stream()) .collect(toSet()); var shapesToSerialize = TopDownIndex.of(model).getContainedOperations(service).stream() .map(it -> model.expectShape(it.getOutputShape(), StructureShape.class)) .flatMap(it -> getShapesToSerde(model, it).stream()) .collect(toSet()); delegator.useFileWriter("service.go", namespace, GoWriter.ChainWritable.of( new NotImplementedError(), new ServerInterface(directive.model(), directive.service(), directive.symbolProvider()), new NoopServiceStruct(directive.model(), directive.service(), directive.symbolProvider()), new RequestHandler(protocolGenerator) ).compose()); delegator.useFileWriter("options.go", namespace, new OptionsStruct(protocolGenerator)); delegator.useFileWriter("deserialize.go", namespace, protocolGenerator.generateDeserializers(shapesToDeserialize)); delegator.useFileWriter("serialize.go", namespace, protocolGenerator.generateSerializers(shapesToSerialize)); delegator.useFileWriter("validate.go", namespace, new ServerValidationgenerator().generate(model, service, directive.symbolProvider())); delegator.useFileWriter("protocol.go", namespace, protocolGenerator.generateProtocolSource()); var noDocSerde = goTemplate("type noSmithyDocumentSerde = $T", SmithyGoTypes.Smithy.Document.NoSerde); delegator.useFileWriter("document.go", namespace, noDocSerde); delegator.useFileWriter("types/document.go", "types", noDocSerde); List dependencies = delegator.getDependencies(); delegator.flushWriters(); GoModuleInfo goModuleInfo = new GoModuleInfo.Builder() .goDirective(settings.getGoDirective()) .dependencies(dependencies) .build(); GoModGenerator.writeGoMod(settings, directive.fileManifest(), goModuleInfo); ManifestWriter.writeManifest(settings, model, directive.fileManifest(), goModuleInfo); } @Override public void generateOperation(GenerateOperationDirective directive) { var protocolGenerator = resolveProtocolGenerator(directive.context()); directive.context().writerDelegator().useShapeWriter(directive.shape(), protocolGenerator.generateHandleOperation(directive.shape())); } @Override public void generateStructure(GenerateStructureDirective directive) { // FUTURE is this necessary? it was taken from the client routine if (directive.shape().getId().getNamespace().equals(CodegenUtils.getSyntheticTypeNamespace())) { return; } if (isUnit(directive.shape().getId())) { return; } var delegator = directive.context().writerDelegator(); delegator.useShapeWriter(directive.shape(), writer -> new StructureGenerator( directive.model(), directive.symbolProvider(), writer, directive.service(), directive.shape(), directive.symbolProvider().toSymbol(directive.shape()), null ).run() ); } @Override public void generateError(GenerateErrorDirective directive) { var delegator = directive.context().writerDelegator(); delegator.useShapeWriter(directive.shape(), writer -> new StructureGenerator( directive.model(), directive.symbolProvider(), writer, directive.service(), directive.shape(), directive.symbolProvider().toSymbol(directive.shape()), null ).run() ); } @Override public void generateUnion(GenerateUnionDirective directive) { var delegator = directive.context().writerDelegator(); delegator.useShapeWriter(directive.shape(), writer -> new UnionGenerator(directive.model(), directive.symbolProvider(), directive.shape()) .generateUnion(writer) ); } @Override public void generateEnumShape(GenerateEnumDirective directive) { var delegator = directive.context().writerDelegator(); delegator.useShapeWriter(directive.shape(), writer -> new EnumGenerator(directive.symbolProvider(), writer, (EnumShape) directive.shape()).run() ); } @Override public void generateIntEnumShape(GenerateIntEnumDirective directive) { directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> new IntEnumGenerator(directive.symbolProvider(), writer, (IntEnumShape) directive.shape()).run() ); } private ServerProtocolGenerator resolveProtocolGenerator(GoCodegenContext ctx) { var model = ctx.model(); var service = ctx.settings().getService(model); var protocolGenerators = ctx.integrations().stream() .flatMap(it -> it.getServerProtocolGenerators(ctx).stream()) .filter(it -> service.hasTrait(it.getProtocol())) .toList(); if (protocolGenerators.isEmpty()) { throw new CodegenException("could not resolve protocol generator"); } return protocolGenerators.get(0); } } ServerInterface.java000066400000000000000000000063431463735525100367460ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.OperationIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; /** * Generates the interface that describes the service API. */ @SmithyInternalApi public final class ServerInterface implements GoWriter.Writable { public static final String NAME = "Service"; private final Model model; private final ServiceShape service; private final SymbolProvider symbolProvider; private final OperationIndex operationIndex; public ServerInterface(Model model, ServiceShape service, SymbolProvider symbolProvider) { this.model = model; this.service = service; this.symbolProvider = symbolProvider; this.operationIndex = OperationIndex.of(model); } @Override public void accept(GoWriter writer) { writer.write(generateInterface()); } private GoWriter.Writable generateInterface() { return goTemplate(""" type $L interface { $W } """, NAME, generateOperations()); } private GoWriter.Writable generateOperations() { return GoWriter.ChainWritable.of( TopDownIndex.of(model).getContainedOperations(service).stream() .filter(op -> !ServerCodegenUtil.operationHasEventStream( model, operationIndex.expectInputShape(op), operationIndex.expectOutputShape(op))) .map(this::generateOperation) .toList() ).compose(false); } private GoWriter.Writable generateOperation(OperationShape operation) { return goTemplate( "$operation:L($context:T, $input:P) ($output:P, error)", MapUtils.of( "operation", symbolProvider.toSymbol(operation).getName(), "context", GoStdlibTypes.Context.Context, "input", symbolProvider.toSymbol(model.expectShape(operation.getInputShape())), "output", symbolProvider.toSymbol(model.expectShape(operation.getOutputShape())) ) ); } } ServerProtocolGenerator.java000066400000000000000000000027211463735525100405120ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server; import java.util.Set; import software.amazon.smithy.go.codegen.ApplicationProtocol; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public interface ServerProtocolGenerator { // Smithy ApplicationProtocol getApplicationProtocol(); ShapeId getProtocol(); // Go GoWriter.Writable generateHandleRequest(); GoWriter.Writable generateHandleOperation(OperationShape operation); GoWriter.Writable generateOptions(); GoWriter.Writable generateDeserializers(Set shape); GoWriter.Writable generateSerializers(Set shape); GoWriter.Writable generateProtocolSource(); } ServerValidationgenerator.java000066400000000000000000000327331463735525100410510ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ /* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.CodegenUtils; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; import software.amazon.smithy.go.codegen.knowledge.GoValidationIndex; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.CollectionShape; 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.ServiceShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.UnionShape; import software.amazon.smithy.model.traits.EnumTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.utils.SmithyInternalApi; import software.amazon.smithy.utils.StringUtils; @SmithyInternalApi public final class ServerValidationgenerator { public GoWriter.Writable generate(Model model, ServiceShape service, SymbolProvider symbolProvider) { return writer -> execute(writer, model, symbolProvider, service); } private void execute(GoWriter writer, Model model, SymbolProvider symbolProvider, ServiceShape service) { GoValidationIndex validationIndex = model.getKnowledge(GoValidationIndex.class); Map inputShapeToOperation = new TreeMap<>(); validationIndex.getOperationsRequiringValidation(service).forEach(shapeId -> { OperationShape operationShape = model.expectShape(shapeId).asOperationShape().get(); Shape inputShape = model.expectShape(operationShape.getInput().get()); inputShapeToOperation.put(inputShape, operationShape); }); Set shapesWithHelpers = validationIndex.getShapesRequiringValidationHelpers(service); generateShapeValidationFunctions(writer, model, symbolProvider, inputShapeToOperation.keySet(), shapesWithHelpers); } private void generateShapeValidationFunctions( GoWriter writer, Model model, SymbolProvider symbolProvider, Set operationInputShapes, Set shapesWithHelpers ) { GoPointableIndex pointableIndex = GoPointableIndex.of(model); for (ShapeId shapeId : shapesWithHelpers) { Shape shape = model.expectShape(shapeId); boolean topLevelShape = operationInputShapes.contains(shape); String functionName = getShapeValidatorName(shape, topLevelShape); Symbol shapeSymbol = symbolProvider.toSymbol(shape); writer.openBlock("func $L(v $P) error {", "}", functionName, shapeSymbol, () -> { writer.addUseImports(SmithyGoDependency.SMITHY); if (pointableIndex.isNillable(shape)) { writer.openBlock("if v == nil {", "}", () -> writer.write("return nil")); } writer.write("invalidParams := smithy.InvalidParamsError{Context: $S}", shapeSymbol.getName()); switch (shape.getType()) { case STRUCTURE: shape.members().forEach(memberShape -> { if (StreamingTrait.isEventStream(model, memberShape)) { return; } String memberName = symbolProvider.toMemberName(memberShape); Shape targetShape = model.expectShape(memberShape.getTarget()); boolean required = GoValidationIndex.isRequiredParameter(model, memberShape, topLevelShape); boolean hasHelper = shapesWithHelpers.contains(targetShape.getId()); boolean isEnum = targetShape.getTrait(EnumTrait.class).isPresent(); if (required) { Runnable runnable = () -> { writer.write("invalidParams.Add(smithy.NewErrParamRequired($S))", memberName); if (hasHelper) { writer.writeInline("} else "); } else { writer.write("}"); } }; if (isEnum) { writer.write("if len(v.$L) == 0 {", memberName); runnable.run(); } else if (pointableIndex.isNillable(memberShape)) { writer.write("if v.$L == nil {", memberName); runnable.run(); } } if (hasHelper) { Runnable runnable = () -> { String helperName = getShapeValidatorName(targetShape, false); writer.openBlock("if err := $L(v.$L); err != nil {", "}", helperName, memberName, () -> { writer.addUseImports(SmithyGoDependency.SMITHY); writer.write( "invalidParams.AddNested($S, err.(smithy.InvalidParamsError))", memberName); }); }; if (isEnum) { writer.openBlock("if len(v.$L) > 0 {", "}", memberName, runnable); } else if (pointableIndex.isNillable(memberShape)) { writer.openBlock("if v.$L != nil {", "}", memberName, runnable); } } }); break; case LIST: case SET: CollectionShape collectionShape = CodegenUtils.expectCollectionShape(shape); MemberShape member = collectionShape.getMember(); Shape memberTarget = model.expectShape(member.getTarget()); String helperName = getShapeValidatorName(memberTarget, false); writer.openBlock("for i := range v {", "}", () -> { String addr = ""; if (!pointableIndex.isPointable(member) && pointableIndex.isPointable(memberTarget)) { addr = "&"; } writer.openBlock("if err := $L($Lv[i]); err != nil {", "}", helperName, addr, () -> { writer.addUseImports(SmithyGoDependency.SMITHY); writer.addUseImports(SmithyGoDependency.FMT); writer.write("invalidParams.AddNested(fmt.Sprintf(\"[%d]\", i), " + "err.(smithy.InvalidParamsError))"); }); }); break; case MAP: MapShape mapShape = shape.asMapShape().get(); MemberShape mapValue = mapShape.getValue(); Shape valueTarget = model.expectShape(mapValue.getTarget()); helperName = getShapeValidatorName(valueTarget, false); writer.openBlock("for key := range v {", "}", () -> { String valueVar = "v[key]"; if (!pointableIndex.isPointable(mapValue) && pointableIndex.isPointable(valueTarget)) { writer.write("value := $L", valueVar); valueVar = "&value"; } writer.openBlock("if err := $L($L); err != nil {", "}", helperName, valueVar, () -> { writer.addUseImports(SmithyGoDependency.SMITHY); writer.addUseImports(SmithyGoDependency.FMT); writer.write("invalidParams.AddNested(fmt.Sprintf(\"[%q]\", key), " + "err.(smithy.InvalidParamsError))"); }); }); break; case UNION: UnionShape unionShape = shape.asUnionShape().get(); Symbol unionSymbol = symbolProvider.toSymbol(unionShape); Set memberShapes = unionShape.getAllMembers().values().stream() .filter(memberShape -> shapesWithHelpers.contains(model.expectShape(memberShape.getTarget()).getId())) .collect(Collectors.toCollection(TreeSet::new)); if (memberShapes.size() > 0) { writer.openBlock("switch uv := v.(type) {", "}", () -> { // Use a TreeSet to sort the members. for (MemberShape unionMember : memberShapes) { Shape target = model.expectShape(unionMember.getTarget()); Symbol memberSymbol = SymbolUtils.createValueSymbolBuilder( symbolProvider.toMemberName(unionMember), unionSymbol.getNamespace() ).build(); String memberHelper = getShapeValidatorName(target, false); writer.openBlock("case *$T:", "", memberSymbol, () -> { String addr = ""; if (!pointableIndex.isPointable(unionMember) && pointableIndex.isPointable(target)) { addr = "&"; } writer.openBlock("if err := $L($Luv.Value); err != nil {", "}", memberHelper, addr, () -> { writer.addUseImports(SmithyGoDependency.SMITHY); writer.write("invalidParams.AddNested(\"[$L]\", " + "err.(smithy.InvalidParamsError))", unionMember.getMemberName()); }); }); } }); } break; default: throw new CodegenException("Unexpected validation helper shape type " + shape.getType()); } writer.write("if invalidParams.Len() > 0 {"); writer.write("return invalidParams"); writer.write("} else {"); writer.write("return nil"); writer.write("}"); }); writer.write(""); } } public static String getShapeValidatorName(Shape shape) { return getShapeValidatorName(shape, false); } public static String getShapeValidatorName(Shape shape, boolean topLevelOpShape) { StringBuilder builder = new StringBuilder(); builder.append("validate"); builder.append(StringUtils.capitalize(shape.getId().getName())); return builder.toString(); } } integration/000077500000000000000000000000001463735525100353315ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serverDefaultProtocols.java000066400000000000000000000027561463735525100414770ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/integration/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server.integration; import java.util.List; import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.go.codegen.server.ServerProtocolGenerator; import software.amazon.smithy.go.codegen.server.protocol.aws.AwsJson10ProtocolGenerator; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public class DefaultProtocols implements GoIntegration { @Override public GoSettings.ArtifactType getArtifactType() { return GoSettings.ArtifactType.SERVER; } @Override public List getServerProtocolGenerators(GoCodegenContext ctx) { return ListUtils.of( new AwsJson10ProtocolGenerator(ctx) ); } } protocol/000077500000000000000000000000001463735525100346475ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/serverHttpHandlerProtocolGenerator.java000066400000000000000000000200341463735525100433170ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/protocol/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server.protocol; import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.ApplicationProtocol; import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.knowledge.GoValidationIndex; import software.amazon.smithy.go.codegen.server.RequestHandler; import software.amazon.smithy.go.codegen.server.ServerProtocolGenerator; import software.amazon.smithy.go.codegen.server.ServerValidationgenerator; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; /** * Base class for HTTP protocol codegen. * HTTP protocols serve requests by generating a net/http.Handler implementation onto the base RequestHandler struct. */ @SmithyInternalApi public abstract class HttpHandlerProtocolGenerator implements ServerProtocolGenerator { protected final GoCodegenContext ctx; private final GoValidationIndex validationIndex; protected HttpHandlerProtocolGenerator(GoCodegenContext ctx) { this.ctx = ctx; this.validationIndex = GoValidationIndex.of(ctx.model()); } @Override public ApplicationProtocol getApplicationProtocol() { return ApplicationProtocol.createDefaultHttpApplicationProtocol(); } @Override public GoWriter.Writable generateHandleRequest() { return goTemplate(""" var _ $httpHandler:T = (*$requestHandler:L)(nil) $serveHttp:W """, MapUtils.of( "requestHandler", RequestHandler.NAME, "httpHandler", GoStdlibTypes.Net.Http.Handler, "serveHttp", generateServeHttp() )); } @Override public GoWriter.Writable generateOptions() { return goTemplate(""" Interceptors HTTPInterceptors """); } @Override public GoWriter.Writable generateProtocolSource() { return goTemplate(""" type InterceptBeforeDeserialize interface { BeforeDeserialize($ctx:T, string, $r:P) error } type InterceptAfterDeserialize interface { AfterDeserialize($ctx:T, string, interface{}) error } type InterceptBeforeSerialize interface { BeforeSerialize($ctx:T, string, interface{}) error } type InterceptBeforeWriteResponse interface { BeforeWriteResponse($ctx:T, string, $w:T) error } type HTTPInterceptors struct { BeforeDeserialize []InterceptBeforeDeserialize AfterDeserialize []InterceptAfterDeserialize BeforeSerialize []InterceptBeforeSerialize BeforeWriteResponse []InterceptBeforeWriteResponse } """, MapUtils.of( "ctx", GoStdlibTypes.Context.Context, "w", GoStdlibTypes.Net.Http.ResponseWriter, "r", GoStdlibTypes.Net.Http.Request )); } @Override public final GoWriter.Writable generateHandleOperation(OperationShape operation) { var service = ctx.settings().getService(ctx.model()); var input = ctx.model().expectShape(operation.getInputShape()); return goTemplate(""" func (h *$requestHandler:L) $funcName:L(w $rw:T, r $r:P) { id, err := $newUuid:T($rand:T).GetUUID() if err != nil { serializeError(w, err) return } $beforeDeserialize:W $deserialize:W $afterDeserialize:W $validate:W out, err := h.service.$operation:L(r.Context(), in) if err != nil { serializeError(w, err) return } $beforeSerialize:W $beforeWriteResponse:W $serialize:W } """, MapUtils.of( "requestHandler", RequestHandler.NAME, "funcName", getOperationHandlerName(operation), "rw", GoStdlibTypes.Net.Http.ResponseWriter, "r", GoStdlibTypes.Net.Http.Request ), MapUtils.of( "newUuid", SmithyGoTypes.Rand.NewUUID, "rand", GoStdlibTypes.Crypto.Rand.Reader, "deserialize", generateDeserializeRequest(operation), "validate", validationIndex.operationRequiresValidation(service, operation) ? generateValidateInput(input) : emptyGoTemplate(), "operation", ctx.symbolProvider().toSymbol(operation).getName(), "serialize", generateSerializeResponse(operation), "beforeDeserialize", generateInvokeInterceptor("BeforeDeserialize", "r"), "afterDeserialize", generateInvokeInterceptor("AfterDeserialize", "in"), "beforeSerialize", generateInvokeInterceptor("BeforeSerialize", "out"), "beforeWriteResponse", generateInvokeInterceptor("BeforeWriteResponse", "w") )); } /** * Generates the net/http.Handler's ServeHTTP implementation for this protocol. * Individual operation handlers are generated by generateServeHttpOperation. Implementors should fill in logic here * to route requests to those methods according to the protocol. */ public abstract GoWriter.Writable generateServeHttp(); /** * Generates a block of logic to convert the input http.Request `r` into the modeled input structure `in`. */ public abstract GoWriter.Writable generateDeserializeRequest(OperationShape operation); /** * Generates a block of serialize the modeled output structure `out` to the http.ResponseWriter `w`. */ public abstract GoWriter.Writable generateSerializeResponse(OperationShape operation); protected final String getOperationHandlerName(OperationShape operation) { return "serveHTTP" + operation.getId().getName(); } private GoWriter.Writable generateValidateInput(Shape input) { return goTemplate(""" if err := $L(in); err != nil { serializeError(w, err) return } """, ServerValidationgenerator.getShapeValidatorName(input)); } private GoWriter.Writable generateInvokeInterceptor(String type, String args) { return goTemplate(""" for _, i := range h.options.Interceptors.$1L { if err := i.$1L(r.Context(), id, $2L); err != nil { serializeError(w, err) return } } """, type, args); } } JsonDeserializerGenerator.java000066400000000000000000000344051463735525100426430ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/protocol/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server.protocol; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.SymbolUtils.getReference; import static software.amazon.smithy.go.codegen.SymbolUtils.isPointable; import static software.amazon.smithy.go.codegen.server.ServerCodegenUtil.normalize; import java.util.Set; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.CollectionShape; import software.amazon.smithy.model.shapes.MapShape; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public final class JsonDeserializerGenerator { private final Model model; private final SymbolProvider symbolProvider; public JsonDeserializerGenerator(Model model, SymbolProvider symbolProvider) { this.model = model; this.symbolProvider = symbolProvider; } public static String getDeserializerName(Shape shape) { return "deserialize" + shape.getId().getName(); } public GoWriter.Writable generate(Set shapes) { return GoWriter.ChainWritable.of( shapes.stream() .map(this::generateShapeDeserializer) .toList() ).compose(); } private GoWriter.Writable generateShapeDeserializer(Shape shape) { return goTemplate(""" func $name:L(v interface{}) ($shapeType:P, error) { av, ok := v.($assert:W) if !ok { return $zero:W, $error:T("invalid") } $deserialize:W } """, MapUtils.of( "name", getDeserializerName(shape), "shapeType", symbolProvider.toSymbol(shape), "assert", generateOpaqueAssert(shape), "zero", generateZeroValue(shape), "error", GoStdlibTypes.Fmt.Errorf, "deserialize", generateDeserializeAssertedValue(shape, "av") )); } private GoWriter.Writable generateOpaqueAssert(Shape shape) { return switch (shape.getType()) { case BYTE, SHORT, INTEGER, LONG, FLOAT, DOUBLE, INT_ENUM -> goTemplate("$T", GoStdlibTypes.Encoding.Json.Number); case STRING, BLOB, TIMESTAMP, ENUM, BIG_DECIMAL, BIG_INTEGER -> goTemplate("string"); case BOOLEAN -> goTemplate("bool"); case LIST, SET -> goTemplate("[]interface{}"); case MAP, STRUCTURE, UNION -> goTemplate("map[string]interface{}"); default -> throw new CodegenException("Unsupported: " + shape.getType()); }; } private GoWriter.Writable generateZeroValue(Shape shape) { return switch (shape.getType()) { case BYTE, SHORT, INTEGER, LONG, FLOAT, DOUBLE -> goTemplate("0"); case STRING -> goTemplate("\"\""); case BOOLEAN -> goTemplate("false"); case BLOB, LIST, SET, MAP, STRUCTURE, UNION -> goTemplate("nil"); case ENUM -> goTemplate("$T(\"\")", symbolProvider.toSymbol(shape)); case INT_ENUM -> goTemplate("$T(0)", symbolProvider.toSymbol(shape)); case TIMESTAMP -> goTemplate("$T{}", GoStdlibTypes.Time.Time); default -> throw new CodegenException("Unsupported: " + shape.getType()); }; } private GoWriter.Writable generateDeserializeAssertedValue(Shape shape, String ident) { return switch (shape.getType()) { case BYTE -> generateDeserializeIntegral(ident, "int8", Byte.MIN_VALUE, Byte.MAX_VALUE); case SHORT -> generateDeserializeIntegral(ident, "int16", Short.MIN_VALUE, Short.MAX_VALUE); case INTEGER -> generateDeserializeIntegral(ident, "int32", Integer.MIN_VALUE, Integer.MAX_VALUE); case LONG -> generateDeserializeIntegral(ident, "int64", Long.MIN_VALUE, Long.MAX_VALUE); case STRING, BOOLEAN -> goTemplate("return $L, nil", ident); case ENUM -> goTemplate("return $T($L), nil", symbolProvider.toSymbol(shape), ident); case BLOB -> goTemplate(""" p, err := $b64:T.DecodeString($ident:L) if err != nil { return nil, err } return p, nil """, MapUtils.of( "ident", ident, "b64", GoStdlibTypes.Encoding.Base64.StdEncoding )); case LIST, SET -> { var target = normalize(model.expectShape(((CollectionShape) shape).getMember().getTarget())); var symbol = symbolProvider.toSymbol(shape); var targetSymbol = symbolProvider.toSymbol(target); yield goTemplate(""" var deserializedList $type:T for _, serializedItem := range $ident:L { deserializedItem, err := $deserialize:L(serializedItem) if err != nil { return nil, err } deserializedList = append(deserializedList, $deref:L) } return deserializedList, nil """, MapUtils.of( "type", symbol, "ident", ident, "deserialize", getDeserializerName(target), "deref", isPointable(getReference(symbol)) != isPointable(targetSymbol) ? "*deserializedItem" : "deserializedItem" )); } case MAP -> { var value = normalize(model.expectShape(((MapShape) shape).getValue().getTarget())); var symbol = symbolProvider.toSymbol(shape); var valueSymbol = symbolProvider.toSymbol(value); yield goTemplate(""" deserializedMap := $type:T{} for key, serializedValue := range $ident:L { deserializedValue, err := $deserialize:L(serializedValue) if err != nil { return nil, err } deserializedMap[key] = $deref:L } return deserializedMap, nil """, MapUtils.of( "type", symbol, "ident", ident, "deserialize", getDeserializerName(value), "deref", isPointable(getReference(symbol)) != isPointable(valueSymbol) ? "*deserializedValue" : "deserializedValue" )); } case STRUCTURE -> goTemplate(""" deserializedStruct := &$type:T{} for key, serializedValue := range $ident:L { $deserializeFields:W } return deserializedStruct, nil """, MapUtils.of( "type", symbolProvider.toSymbol(shape), "ident", ident, "deserializeFields", GoWriter.ChainWritable.of( shape.getAllMembers().entrySet().stream() .map(it -> { var target = model.expectShape(it.getValue().getTarget()); return goTemplate(""" if key == $field:S { fieldValue, err := $deserialize:L(serializedValue) if err != nil { return nil, err } deserializedStruct.$fieldName:L = $deref:W } """, MapUtils.of( "field", it.getKey(), "fieldName", symbolProvider.toMemberName(it.getValue()), "deserialize", getDeserializerName(normalize(target)), "deref", generateStructFieldDeref( it.getValue(), "fieldValue") )); }) .toList() ).compose(false) )); case UNION -> goTemplate(""" for key, serializedValue := range $ident:L { $deserializeVariant:W } """, MapUtils.of( "type", symbolProvider.toSymbol(shape), "ident", ident, "deserializeVariant", GoWriter.ChainWritable.of( shape.getAllMembers().entrySet().stream() .map(it -> { var target = model.expectShape(it.getValue().getTarget()); return goTemplate(""" if key == $variant:S { variant, err := $deserialize:L(serializedValue) if err != nil { return nil, err } return variant, nil } """, MapUtils.of( "variant", it.getKey(), "deserialize", getDeserializerName(normalize(target)) )); }) .toList() ).compose(false) )); case TIMESTAMP -> goTemplate(""" dts, err := $T(serializedValue) if err != nil { return nil, err } return dts, nil """, SmithyGoTypes.Time.ParseDateTime); default -> throw new CodegenException("Unsupported: " + shape.getType()); }; } private GoWriter.Writable generateDeserializeIntegral(String ident, String castTo, long min, long max) { return goTemplate(""" $nextident:L, err := $ident:L.Int64() if err != nil { return 0, err } if $nextident:L < $min:L || $nextident:L > $max:L { return 0, $errorf:T("invalid") } return $cast:L($nextident:L), nil """, MapUtils.of( "errorf", GoStdlibTypes.Fmt.Errorf, "ident", ident, "nextident", ident + "_", "min", min, "max", max, "cast", castTo )); } private GoWriter.Writable generateStructFieldDeref(MemberShape member, String ident) { var symbol = symbolProvider.toSymbol(member); if (!isPointable(symbol)) { return goTemplate(ident); } return switch (model.expectShape(member.getTarget()).getType()) { case BYTE -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Int8, ident); case SHORT -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Int16, ident); case INTEGER -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Int32, ident); case LONG -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Int64, ident); case STRING -> goTemplate("$T($L)", SmithyGoTypes.Ptr.String, ident); case BOOLEAN -> goTemplate("$T($L)", SmithyGoTypes.Ptr.Bool, ident); default -> goTemplate(ident); }; } } JsonSerializerGenerator.java000066400000000000000000000230001463735525100423170ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/protocol/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server.protocol; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.SymbolUtils.getReference; import static software.amazon.smithy.go.codegen.SymbolUtils.isPointable; import static software.amazon.smithy.go.codegen.server.ServerCodegenUtil.normalize; import java.util.Set; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.CollectionShape; import software.amazon.smithy.model.shapes.MapShape; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.TimestampShape; import software.amazon.smithy.model.shapes.UnionShape; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; @SmithyInternalApi public final class JsonSerializerGenerator { private final Model model; private final SymbolProvider symbolProvider; public JsonSerializerGenerator(Model model, SymbolProvider symbolProvider) { this.model = model; this.symbolProvider = symbolProvider; } public static String getSerializerName(Shape shape) { return "serialize" + shape.getId().getName(); } public GoWriter.Writable generate(Set shapes) { return GoWriter.ChainWritable.of( shapes.stream() .map(this::generateShapeSerializer) .toList() ).compose(); } private GoWriter.Writable generateShapeSerializer(Shape shape) { return goTemplate(""" func $name:L(v $shapeType:P, jv $jsonValue:T) (error) { $serialize:W return nil } """, MapUtils.of( "name", getSerializerName(shape), "shapeType", symbolProvider.toSymbol(shape), "jsonValue", SmithyGoTypes.Encoding.Json.Value, "serialize", generateSerializeValue(shape) )); } private GoWriter.Writable generateSerializeValue(Shape shape) { return switch (shape.getType()) { case BYTE -> goTemplate("jv.Byte(v)"); case SHORT -> goTemplate("jv.Short(v)"); case INTEGER -> goTemplate("jv.Integer(v)"); case LONG -> goTemplate("jv.Long(v)"); case FLOAT -> goTemplate("jv.Float(v)"); case DOUBLE -> goTemplate("jv.Double(v)"); case STRING -> goTemplate("jv.String(v)"); case BOOLEAN -> goTemplate("jv.Boolean(v)"); case BLOB -> goTemplate("jv.Base64EncodeBytes(v)"); case ENUM -> goTemplate("jv.String(string(v))"); case INT_ENUM -> goTemplate("jv.Integer(int32(v))"); case TIMESTAMP -> generateSerializeTimestamp((TimestampShape) shape); case LIST, SET -> generateSerializeList((CollectionShape) shape); case MAP -> generateSerializeMap((MapShape) shape); case STRUCTURE -> generateSerializeStruct((StructureShape) shape); case UNION -> generateSerializeUnion((UnionShape) shape); default -> throw new CodegenException("Unsupported: " + shape.getType()); }; } private GoWriter.Writable generateSerializeTimestamp(TimestampShape shape) { return goTemplate(""" if v != nil { jv.String($T(*v)) } """, SmithyGoTypes.Time.FormatDateTime); } private GoWriter.Writable generateSerializeList(CollectionShape shape) { var target = normalize(model.expectShape(shape.getMember().getTarget())); var symbol = symbolProvider.toSymbol(shape); var targetSymbol = symbolProvider.toSymbol(target); return goTemplate(""" a := jv.Array() defer a.Close() for i := range v { av := a.Value() if err := $serialize:L($indirect:L, av); err != nil { return err } } """, MapUtils.of( "serialize", getSerializerName(target), "indirect", isPointable(getReference(symbol)) != isPointable(targetSymbol) ? "&v[i]" : "v[i]" )); } private GoWriter.Writable generateSerializeMap(MapShape shape) { var value = normalize(model.expectShape(shape.getValue().getTarget())); var symbol = symbolProvider.toSymbol(shape); var valueSymbol = symbolProvider.toSymbol(value); return goTemplate(""" mp := jv.Object() defer mp.Close() for k, vv := range v { mv := mp.Key(k) if err := $serialize:L($indirect:L, mv); err != nil { return err } } """, MapUtils.of( "serialize", getSerializerName(value), "indirect", isPointable(getReference(symbol)) != isPointable(valueSymbol) ? "&vv" : "vv" )); } private GoWriter.Writable generateSerializeStruct(StructureShape shape) { return goTemplate(""" mp := jv.Object() defer mp.Close() $W """, GoWriter.ChainWritable.of( shape.getAllMembers().values().stream() .map(this::generateSerializeField) .toList() ).compose(false)); } private GoWriter.Writable generateSerializeField(MemberShape member) { var symbol = symbolProvider.toSymbol(member); var target = normalize(model.expectShape(member.getTarget())); return switch (target.getType()) { case BYTE, SHORT, INTEGER, LONG, FLOAT, DOUBLE, STRING, BOOLEAN -> isPointable(symbol) ? serializeNilableMember(member, target, true) : serializeMember(member, target); case BLOB, LIST, SET, MAP, STRUCTURE, UNION -> serializeNilableMember(member, target, false); default -> serializeMember(member, target); }; } private GoWriter.Writable serializeNilableMember(MemberShape member, Shape target, boolean deref) { return goTemplate(""" if v.$field:L != nil { if err := $serialize:L($deref:L v.$field:L, mp.Key($key:S)); err != nil { return err } } """, MapUtils.of( "field", symbolProvider.toMemberName(member), "key", member.getMemberName(), "serialize", getSerializerName(target), "deref", deref ? "*" : "" )); } private GoWriter.Writable serializeMember(MemberShape member, Shape target) { return goTemplate(""" if err := $serialize:L(v.$field:L, mp.Key($key:S)); err != nil { return err } """, MapUtils.of( "field", symbolProvider.toMemberName(member), "key", member.getMemberName(), "serialize", getSerializerName(target) )); } private GoWriter.Writable generateSerializeUnion(UnionShape shape) { return goTemplate(""" mp := jv.Object() defer mp.Close() $W """, GoWriter.ChainWritable.of( shape.getAllMembers().values().stream() .map(this::generateSerializeVariant) .toList() ).compose(false)); } private GoWriter.Writable generateSerializeVariant(MemberShape member) { var target = normalize(model.expectShape(member.getTarget())); return goTemplate(""" if variant, ok := v.($variant:P); ok { if err := $serialize:L(variant, mp.Key($key:S)); err != nil { return err } } """, MapUtils.of( "variant", symbolProvider.toSymbol(target), "serialize", getSerializerName(target), "key", member.getMemberName() )); } } aws/000077500000000000000000000000001463735525100354415ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/protocolAwsJson10ProtocolGenerator.java000066400000000000000000000217621463735525100434320ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/server/protocol/aws/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.server.protocol.aws; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.server.protocol.JsonDeserializerGenerator.getDeserializerName; import static software.amazon.smithy.go.codegen.server.protocol.JsonSerializerGenerator.getSerializerName; import java.util.Set; import software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait; import software.amazon.smithy.go.codegen.GoCodegenContext; import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.server.NotImplementedError; import software.amazon.smithy.go.codegen.server.RequestHandler; import software.amazon.smithy.go.codegen.server.ServerCodegenUtil; import software.amazon.smithy.go.codegen.server.protocol.HttpHandlerProtocolGenerator; import software.amazon.smithy.go.codegen.server.protocol.JsonDeserializerGenerator; import software.amazon.smithy.go.codegen.server.protocol.JsonSerializerGenerator; import software.amazon.smithy.model.knowledge.OperationIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.shapes.OperationShape; 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.HttpErrorTrait; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyInternalApi; /** * Implements the aws.protocols#awsJson1_0 protocol. */ @SmithyInternalApi public final class AwsJson10ProtocolGenerator extends HttpHandlerProtocolGenerator { private final OperationIndex operationIndex; public AwsJson10ProtocolGenerator(GoCodegenContext ctx) { super(ctx); this.operationIndex = OperationIndex.of(ctx.model()); } @Override public ShapeId getProtocol() { return AwsJson1_0Trait.ID; } @Override public GoWriter.Writable generateDeserializers(Set shapes) { return new JsonDeserializerGenerator(ctx.model(), ctx.symbolProvider()).generate(shapes); } @Override public GoWriter.Writable generateSerializers(Set shapes) { return GoWriter.ChainWritable.of( new JsonSerializerGenerator(ctx.model(), ctx.symbolProvider()).generate(shapes), generateSerializeError() ).compose(); } @Override public GoWriter.Writable generateServeHttp() { return goTemplate(""" func (h *$requestHandler:L) ServeHTTP(w $rw:T, r $r:P) { w.Header().Set("Content-Type", "application/x-amz-json-1.0") if r.Method != http.MethodPost { writeEmpty(w, http.StatusNotFound) return } target := r.Header.Get("X-Amz-Target") $route:W writeEmpty(w, http.StatusNotFound) } """, MapUtils.of( "newUuid", SmithyGoTypes.Rand.NewUUID, "rand", GoStdlibTypes.Crypto.Rand.Reader, "requestHandler", RequestHandler.NAME, "rw", GoStdlibTypes.Net.Http.ResponseWriter, "r", GoStdlibTypes.Net.Http.Request, "route", generateRouteRequest() )); } private GoWriter.Writable generateRouteRequest() { var model = ctx.model(); var service = ctx.settings().getService(ctx.model()); return GoWriter.ChainWritable.of( TopDownIndex.of(model).getContainedOperations(service).stream() .filter(op -> !ServerCodegenUtil.operationHasEventStream( model, operationIndex.expectInputShape(op), operationIndex.expectOutputShape(op))) .map(it -> goTemplate(""" if target == $S { h.$L(w, r) return } """, getOperationTarget(it), getOperationHandlerName(it))) .toList() ).compose(false); } @Override public GoWriter.Writable generateDeserializeRequest(OperationShape operation) { return goTemplate(""" d := $decoder:T(r.Body) d.UseNumber() var jv map[string]interface{} if err := d.Decode(&jv); err != nil { serializeError(w, err) return } in, err := $deserialize:L(jv) if err != nil { serializeError(w, err) return } """, MapUtils.of( "decoder", GoStdlibTypes.Encoding.Json.NewDecoder, "deserialize", getDeserializerName(ctx.model().expectShape(operation.getInputShape())) )); } @Override public GoWriter.Writable generateSerializeResponse(OperationShape operation) { return goTemplate(""" e := $encoder:T() if err := $serialize:L(out, e.Value); err != nil { serializeError(w, err) return } w.WriteHeader(http.StatusOK) w.Write(e.Bytes()) return """, MapUtils.of( "encoder", SmithyGoTypes.Encoding.Json.NewEncoder, "serialize", getSerializerName(ctx.model().expectShape(operation.getOutputShape())) )); } private GoWriter.Writable generateSerializeError() { var errorShapes = ctx.model().getStructureShapesWithTrait(ErrorTrait.class); return goTemplate(""" func serializeError(w $rw:T, err error) { if _, ok := err.($invalidParams:T); ok { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"__type":"InvalidRequest"}`)) return } if _, ok := err.(*$notImplemented:L); ok { writeEmpty(w, http.StatusNotImplemented) return } $serializeErrors:W writeEmpty(w, http.StatusInternalServerError) } func writeEmpty(w $rw:T, status int) { w.WriteHeader(status) w.Write([]byte("{}")) } """, MapUtils.of( "rw", GoStdlibTypes.Net.Http.ResponseWriter, "invalidParams", SmithyGoTypes.Smithy.InvalidParamsError, "notImplemented", NotImplementedError.NAME, "serializeErrors", generateSerializeErrors(errorShapes) )); } // FUTURE only generate errors that apply to an operation private GoWriter.Writable generateSerializeErrors(Set errorShapes) { return GoWriter.ChainWritable.of( errorShapes.stream() .map(this::generateSerializeError) .toList() ).compose(false); } private GoWriter.Writable generateSerializeError(StructureShape errorShape) { var httpStatus = errorShape.hasTrait(HttpErrorTrait.class) ? errorShape.expectTrait(HttpErrorTrait.class).getCode() : 400; return goTemplate(""" if _, ok := err.($err:P); ok { w.WriteHeader($status:L) w.Write([]byte(`{"__type":$type:S}`)) return } """, MapUtils.of( "err", ctx.symbolProvider().toSymbol(errorShape), "status", httpStatus, "type", errorShape.getId().toString() )); } private String getOperationTarget(OperationShape operation) { var service = ctx.settings().getService(ctx.model()); return service.getId().getName(service) + "." + operation.getId().getName(service); } } testutils/000077500000000000000000000000001463735525100335405ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegenExecuteCommand.java000066400000000000000000000106371463735525100373130ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/testutils/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.testutils; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import software.amazon.smithy.utils.SmithyBuilder; /** * Utility for invoking command line utilities. */ public final class ExecuteCommand { private static final Logger LOGGER = Logger.getLogger(ExecuteCommand.class.getName()); private final List command; private final File workingDir; private ExecuteCommand(Builder builder) { command = SmithyBuilder.requiredState("command", builder.command); workingDir = builder.workingDir; } /** * Invokes the command in the filepath directory provided. * @param workingDir Directory to execute the command in. * @param command Command to be executed. * @throws Exception if the command fails. */ public static void execute(File workingDir, String... command) throws Exception { ExecuteCommand.builder() .addCommand(command) .workingDir(workingDir) .build() .execute(); } /** * Invokes the command returning the exception if there was any. * @throws Exception if the command fails. */ public void execute() throws Exception { int exitCode; Process child; try { var cmdArray = new String[command.size()]; command.toArray(cmdArray); child = Runtime.getRuntime().exec(cmdArray, null, workingDir); exitCode = child.waitFor(); BufferedReader stdOut = new BufferedReader(new InputStreamReader(child.getInputStream(), Charset.defaultCharset())); BufferedReader stdErr = new BufferedReader(new InputStreamReader(child.getErrorStream(), Charset.defaultCharset())); String s; while ((s = stdOut.readLine()) != null) { LOGGER.info(s); } stdOut.close(); while ((s = stdErr.readLine()) != null) { LOGGER.warning(s); } stdErr.close(); } catch (Exception e) { throw new Exception("Unable to execute command, " + command, e); } if (exitCode != 0) { throw new Exception("Command existed with non-zero code, " + command + ", status code: " + exitCode); } } /** * Returns the builder for ExecuteCommand. * @return ExecuteCommand builder. */ public static Builder builder() { return new Builder(); } /** * ExecuteCommand builder. */ public static final class Builder implements SmithyBuilder { private List command; private File workingDir; private Builder() { } /** * Adds command arguments to the set of arguments the command will be executed with. * @param command command and arguments to be executed. * @return builder */ public Builder addCommand(String... command) { if (this.command == null) { this.command = new ArrayList<>(); } this.command.addAll(List.of(command)); return this; } /** * Sets the working directory for the command. * @param workingDir working directory. * @return builder */ public Builder workingDir(File workingDir) { this.workingDir = workingDir; return this; } /** * Builds the ExecuteCommand. * @return Execute command */ @Override public ExecuteCommand build() { return new ExecuteCommand(this); } } } smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/trait/000077500000000000000000000000001463735525100327025ustar00rootroot00000000000000BackfilledInputOutputTrait.java000066400000000000000000000023111463735525100407500ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/trait/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.trait; import static software.amazon.smithy.model.node.Node.objectNode; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.traits.AnnotationTrait; /** * Marker trait to indicate that an input or output shape on an operation was backfilled (i.e. the operation DID NOT * have a target for it in the original model). */ public class BackfilledInputOutputTrait extends AnnotationTrait { public static final ShapeId ID = ShapeId.from("smithy.go.trait#BackfilledInputOutput"); public BackfilledInputOutputTrait() { super(ID, objectNode()); } } NoSerializeTrait.java000066400000000000000000000045221463735525100367210ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/trait/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. * * */ package software.amazon.smithy.go.codegen.trait; import java.util.function.Predicate; import software.amazon.smithy.model.knowledge.HttpBinding; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.traits.AnnotationTrait; /** * Provides a custom trait for labeling structure members as not serialized. Used to decorate custom API input * parameters that should not be serialized, but are needed for the SDK's customizations. */ public class NoSerializeTrait extends AnnotationTrait { public static final ShapeId ID = ShapeId.from("smithy.go.trait#NoSerialize"); public NoSerializeTrait() { this(Node.objectNode()); } public NoSerializeTrait(ObjectNode node) { super(ID, node); } public static final class Provider extends AnnotationTrait.Provider { public Provider() { super(ID, NoSerializeTrait::new); } } /** * Predicate to filter out members decorated with the NoSerializeTrait from a collection of member shapes. * * @return predicate to filter members. */ public static Predicate excludeNoSerializeMembers() { return member -> !member.hasTrait(NoSerializeTrait.class); } /** * Predicate to filter out HttpBinding members decorated with the NoSerializeTrait from a collection of * HttpBindings. * * @return predicate to filter HttpBinding members. */ public static Predicate excludeNoSerializeHttpBindingMembers() { return binding -> !binding.getMember().hasTrait(NoSerializeTrait.class); } } PagingExtensionTrait.java000066400000000000000000000054701463735525100376020ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/trait/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.trait; import java.util.Optional; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.traits.AbstractTrait; import software.amazon.smithy.model.traits.AbstractTraitBuilder; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.ToSmithyBuilder; /** * Provides a custom runtime trait that can be used to extend the standard the paginator generation behavior. *

* Currently exposes the ability to specify a MemberShape of the operation output that should be checked to determine * if the output was truncated. */ public final class PagingExtensionTrait extends AbstractTrait implements ToSmithyBuilder { public static final ShapeId ID = ShapeId.from("smithy.go.traits#PagingExtensionTrait"); private final MemberShape moreResults; private PagingExtensionTrait(Builder builder) { super(ID, builder.getSourceLocation()); moreResults = builder.moreResults; } /** * Get the output member shape that is used to indicate that there are more results. * * @return the member shape. */ public Optional getMoreResults() { return Optional.ofNullable(moreResults); } @Override protected Node createNode() { throw new CodegenException("attempted to serialize runtime only trait"); } @Override public SmithyBuilder toBuilder() { return new Builder() .moreResults(moreResults); } public static Builder builder() { return new Builder(); } public static final class Builder extends AbstractTraitBuilder { private MemberShape moreResults; private Builder() { } public Builder moreResults(MemberShape moreResults) { this.moreResults = moreResults; return this; } @Override public PagingExtensionTrait build() { return new PagingExtensionTrait(this); } } } UnexportedMemberTrait.java000066400000000000000000000032031463735525100377550ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/trait/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. * * */ package software.amazon.smithy.go.codegen.trait; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.traits.AnnotationTrait; /** * Provides a trait to decorate an member as unexported, so it will not be exported within the containing structure. * This should only be used on top level input/output structure shapes, because internal members of non-input/output * shapes are not visible outside of the API client's "types" package. */ public class UnexportedMemberTrait extends AnnotationTrait { public static final ShapeId ID = ShapeId.from("smithy.go.trait#UnexportedMember"); public UnexportedMemberTrait() { this(Node.objectNode()); } public UnexportedMemberTrait(ObjectNode node) { super(ID, node); } public static final class Provider extends AnnotationTrait.Provider { public Provider() { super(ID, UnexportedMemberTrait::new); } } } smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/000077500000000000000000000000001463735525100325345ustar00rootroot00000000000000NodeUtil.java000066400000000000000000000022651463735525100350500ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.util; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.model.node.ArrayNode; public final class NodeUtil { private NodeUtil() {} public static GoWriter.Writable writableStringSlice(ArrayNode node) { return goTemplate("[]string{$W}", GoWriter.ChainWritable.of( node.getElements().stream() .map(it -> goTemplate("$S,", it.expectStringNode().getValue())) .toList() ).compose(false)); } } ShapeUtil.java000066400000000000000000000036011463735525100352160ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/* * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen.util; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.CollectionShape; import software.amazon.smithy.model.shapes.ListShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.StringShape; public final class ShapeUtil { public static final StringShape STRING_SHAPE = StringShape.builder() .id("smithy.go.synthetic#String") .build(); private ShapeUtil() {} public static ListShape listOf(Shape member) { return ListShape.builder() .id("smithy.go.synthetic#" + member.getId().getName() + "List") .member(member.getId()) .build(); } public static Shape expectMember(Model model, Shape shape, String memberName) { var optMember = shape.getMember(memberName); if (optMember.isEmpty()) { throw new CodegenException("expected member " + memberName + " in shape " + shape); } var member = optMember.get(); return model.expectShape(member.getTarget()); } public static Shape expectMember(Model model, CollectionShape shape) { return model.expectShape(shape.getMember().getTarget()); } } smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/000077500000000000000000000000001463735525100242035ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/META-INF/000077500000000000000000000000001463735525100253435ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/META-INF/services/000077500000000000000000000000001463735525100271665ustar00rootroot00000000000000software.amazon.smithy.build.SmithyBuildPlugin000066400000000000000000000001571463735525100402000ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/META-INF/servicessoftware.amazon.smithy.go.codegen.GoCodegenPlugin software.amazon.smithy.go.codegen.server.ServerCodegenPlugin software.amazon.smithy.go.codegen.integration.GoIntegration000066400000000000000000000015771463735525100425770ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/META-INF/servicessoftware.amazon.smithy.go.codegen.integration.ValidationGenerator software.amazon.smithy.go.codegen.integration.IdempotencyTokenMiddlewareGenerator software.amazon.smithy.go.codegen.integration.AddChecksumRequiredMiddleware software.amazon.smithy.go.codegen.integration.RequiresLengthTraitSupport software.amazon.smithy.go.codegen.integration.EndpointHostPrefixMiddleware software.amazon.smithy.go.codegen.integration.OperationInterfaceGenerator software.amazon.smithy.go.codegen.integration.ClientLogger software.amazon.smithy.go.codegen.endpoints.EndpointClientPluginsGenerator # modeled auth schemes software.amazon.smithy.go.codegen.integration.auth.SigV4AuthScheme software.amazon.smithy.go.codegen.integration.auth.AnonymousAuthScheme software.amazon.smithy.go.codegen.requestcompression.RequestCompression # server software.amazon.smithy.go.codegen.server.integration.DefaultProtocols smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/000077500000000000000000000000001463735525100260355ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/000077500000000000000000000000001463735525100273225ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/000077500000000000000000000000001463735525100306375ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/000077500000000000000000000000001463735525100312445ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/000077500000000000000000000000001463735525100326505ustar00rootroot00000000000000document_doc.go.template000066400000000000000000000046761463735525100374120ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegenPackage document implements encoding and decoding of open-content that has a JSON-like data model. This data-model allows for UTF-8 strings, arbitrary precision numbers, booleans, nulls, a list of these values, and a map of UTF-8 strings to these values. Interface defines the semantics for how a document type is marshalled and unmarshalled for requests and responses for the service. To send a document as input to the service you use NewLazyDocument and pass it the Go type to be sent to the service. NewLazyDocument returns a document Interface type that encodes the provided Go type during the request serialization step after you have invoked an API client operation that uses the document type. The following examples show how you can create document types using basic Go types. NewLazyDocument(map[string]interface{}{ "favoriteNumber": 42, "fruits": []string{"apple", "orange"}, "capitals": map[string]interface{}{ "Washington": "Olympia", "Oregon": "Salem", }, "skyIsBlue": true, }) NewLazyDocument(3.14159) NewLazyDocument([]interface{"One", 2, 3, 3.5, "four"}) NewLazyDocument(true) Services can send document types as part of their API responses. To retrieve the content of a response document you use the UnmarshalSmithyDocument method on the response document. When calling UnmarshalSmithyDocument you pass a reference to the Go type that you want to unmarshal and map the response to. For example, if you expect to receive key/value map from the service response: var kv map[string]interface{} if err := outputDocument.UnmarshalSmithyDocument(&kv); err != nil { // handle error } If a service can return one or more data-types in the response, you can use an empty interface and type switch to dynamically handle the response type. var v interface{} if err := outputDocument.UnmarshalSmithyDocument(&v); err != nil { // handle error } switch vv := v.(type) { case map[string]interface{}: // handle key/value map case []interface{}: // handle array of values case bool: // handle boolean case document.Number: // handle an arbitrary precision number case string: // handle string default: // handle unknown case } The mapping of Go types to document types is covered in more depth in https://pkg.go.dev/github.com/aws/smithy-go/document including more in depth examples that cover user-defined structure types. smithy-tests/000077500000000000000000000000001463735525100352465ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegenenum-shape-test/000077500000000000000000000000001463735525100402655ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-testsenum-shape-test.smithy000066400000000000000000000004251463735525100445440ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/enum-shape-test$version: "2.0" namespace smithy.example service Example { version: "1.0.0" operations: [ ChangeCard ] } operation ChangeCard { input: Card output: Card } structure Card { suit: Suit } enum Suit { DIAMOND CLUB HEART SPADE } expected/000077500000000000000000000000001463735525100420665ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/enum-shape-testtypes/000077500000000000000000000000001463735525100432325ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/enum-shape-test/expectedenums.go000066400000000000000000000010411463735525100447040ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/enum-shape-test/expected/types// Code generated by smithy-go-codegen DO NOT EDIT. package types type Suit string // Enum values for Suit const ( SuitDiamond Suit = "DIAMOND" SuitClub Suit = "CLUB" SuitHeart Suit = "HEART" SuitSpade Suit = "SPADE" ) // Values returns all known values for Suit. Note that this can be expanded in the // future, and so it is only as up to date as the client. // // The ordering of this slice is not guaranteed to be stable across updates. func (Suit) Values() []Suit { return []Suit{ "DIAMOND", "CLUB", "HEART", "SPADE", } } int-enum-shape-test/000077500000000000000000000000001463735525100410555ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-testsexpected/000077500000000000000000000000001463735525100426565ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/int-enum-shape-testchangeCardInput.go.struct000066400000000000000000000001431463735525100475650ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/int-enum-shape-test/expectedtype ChangeCardInput struct { Number types.Number Suit types.Suit noSmithyDocumentSerde } changeCardOutput.go.struct000066400000000000000000000002751463735525100477740ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/int-enum-shape-test/expectedtype ChangeCardOutput struct { Number types.Number Suit types.Suit // Metadata pertaining to the operation's result. ResultMetadata middleware.Metadata noSmithyDocumentSerde } types/000077500000000000000000000000001463735525100440225ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/int-enum-shape-test/expectedenums.go000066400000000000000000000016121463735525100455000ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/int-enum-shape-test/expected/types// Code generated by smithy-go-codegen DO NOT EDIT. package types type Number = int32 // Enum values for Number const ( NumberAce Number = 1 NumberTwo Number = 2 NumberThree Number = 3 NumberFour Number = 4 NumberFive Number = 5 NumberSix Number = 6 NumberSeven Number = 7 NumberEight Number = 8 NumberNine Number = 9 NumberTen Number = 10 NumberJack Number = 11 NumberQueen Number = 12 NumberKing Number = 13 ) type Suit string // Enum values for Suit const ( SuitDiamond Suit = "DIAMOND" SuitClub Suit = "CLUB" SuitHeart Suit = "HEART" SuitSpade Suit = "SPADE" ) // Values returns all known values for Suit. Note that this can be expanded in the // future, and so it is only as up to date as the client. // // The ordering of this slice is not guaranteed to be stable across updates. func (Suit) Values() []Suit { return []Suit{ "DIAMOND", "CLUB", "HEART", "SPADE", } } int-enum-shape-test.smithy000066400000000000000000000007661463735525100461340ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/int-enum-shape-test$version: "2.0" namespace smithy.example service Example { version: "1.0.0" operations: [ ChangeCard ] } operation ChangeCard { input: Card output: Card } structure Card { suit: Suit number: Number } enum Suit { DIAMOND CLUB HEART SPADE } intEnum Number { ACE = 1 TWO = 2 THREE = 3 FOUR = 4 FIVE = 5 SIX = 6 SEVEN = 7 EIGHT = 8 NINE = 9 TEN = 10 JACK = 11 QUEEN = 12 KING = 13 } mixin-test/000077500000000000000000000000001463735525100373475ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-testsexpected/000077500000000000000000000000001463735525100411505ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/mixin-testchangeCardInput.go.mixin000066400000000000000000000001321463735525100456550ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/mixin-test/expectedtype ChangeCardInput struct { Number *int32 Suit *string noSmithyDocumentSerde } changeCardOutput.go.mixin000066400000000000000000000002641463735525100460640ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/mixin-test/expectedtype ChangeCardOutput struct { Number *int32 Suit *string // Metadata pertaining to the operation's result. ResultMetadata middleware.Metadata noSmithyDocumentSerde } mixin-test.smithy000066400000000000000000000004571463735525100427150ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/main/resources/software/amazon/smithy/go/codegen/smithy-tests/mixin-test$version: "2.0" namespace smithy.example service Example { version: "1.0.0" operations: [ ChangeCard ] } operation ChangeCard { input: Card output: Card } @mixin structure CardValuesMixin { suit: String number: Integer } structure Card with [CardValuesMixin] {} smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/000077500000000000000000000000001463735525100222245ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/000077500000000000000000000000001463735525100231455ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/000077500000000000000000000000001463735525100247775ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/000077500000000000000000000000001463735525100262645ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/000077500000000000000000000000001463735525100276015ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/000077500000000000000000000000001463735525100302065ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/000077500000000000000000000000001463735525100316125ustar00rootroot00000000000000AddOperationShapesTest.java000066400000000000000000000213351463735525100367570ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; 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.StringShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.utils.ListUtils; import java.util.Optional; import java.util.logging.Logger; public class AddOperationShapesTest { private static final Logger LOGGER = Logger.getLogger(AddOperationShapesTest.class.getName()); private static final String NAMESPACE = "go.codege.test"; private static final ServiceShape SERVICE = ServiceShape.builder() .id(ShapeId.fromParts(NAMESPACE, "TestService")) .version("1.0") .build(); @Test public void testOperationWithoutInputOutput() { GoSettings settings = new GoSettings(); settings.setService(SERVICE.toShapeId()); OperationShape op = OperationShape.builder() .id(ShapeId.fromParts(NAMESPACE, "TestOperation")) .build(); ServiceShape service = SERVICE.toBuilder() .addOperation(op.getId()) .build(); Model model = Model.builder() .addShapes(service, op) .build(); Model processedModel = AddOperationShapes.execute(model, service.getId()); ListUtils.of( ShapeId.fromParts(CodegenUtils.getSyntheticTypeNamespace(), "TestOperationInput"), ShapeId.fromParts(CodegenUtils.getSyntheticTypeNamespace(), "TestOperationOutput") ).forEach(shapeId -> { Optional shape = processedModel.getShape(shapeId); MatcherAssert.assertThat(shapeId + " shape must be present in model", shape.isPresent(), Matchers.is(true)); MatcherAssert.assertThat(shapeId + " shape must have no members", shape.get().members().size(), Matchers.equalTo(0)); Optional opSynth = shape.get().getTrait(Synthetic.class); if (opSynth.isPresent()) { Synthetic synth = opSynth.get(); MatcherAssert.assertThat(shapeId + " shape must not have archetype", synth.getArchetype().isPresent(), Matchers.equalTo(false)); } else { MatcherAssert.assertThat(shapeId + " shape must be synthetic clone", false); } }); } @Test public void testOperationWithExistingInputOutput() { GoSettings settings = new GoSettings(); settings.setService(SERVICE.toShapeId()); StringShape stringShape = StringShape.builder().id(ShapeId.fromParts(NAMESPACE, "String")).build(); StructureShape inputShape = StructureShape.builder() .id(ShapeId.fromParts(NAMESPACE, "TestOperationRequest")) .addMember("foo", stringShape.getId()) .build(); StructureShape outputShape = StructureShape.builder() .id(ShapeId.fromParts(NAMESPACE, "TestOperationResponse")) .addMember("foo", stringShape.getId()) .build(); OperationShape op = OperationShape.builder() .id(ShapeId.fromParts(NAMESPACE, "TestOperation")) .input(inputShape) .output(outputShape) .build(); ServiceShape service = SERVICE.toBuilder() .addOperation(op.getId()) .build(); Model model = Model.builder() .addShapes(stringShape, inputShape, outputShape, op, service) .build(); Model processedModel = AddOperationShapes.execute(model, service.getId()); ListUtils.of( ShapeId.fromParts(CodegenUtils.getSyntheticTypeNamespace(), "TestOperationInput"), ShapeId.fromParts(CodegenUtils.getSyntheticTypeNamespace(), "TestOperationOutput") ).forEach(shapeId -> { Optional shape = processedModel.getShape(shapeId); MatcherAssert.assertThat(shapeId + " shape must be present in model", shape.isPresent(), Matchers.is(true)); StructureShape structureShape = shape.get().asStructureShape().get(); Optional fooMember = structureShape.getMember("foo"); MatcherAssert.assertThat("foo member present", fooMember.isPresent(), Matchers.is(true)); ShapeId id = fooMember.get().getId(); MatcherAssert.assertThat("foo is correct namespace", id.getNamespace(), Matchers.equalTo(shapeId.getNamespace())); MatcherAssert.assertThat("foo is correct parent", id.getName(service), Matchers.equalTo(shapeId.getName(service))); Optional synthetic = shape.get().getTrait(Synthetic.class); if (!synthetic.isPresent()) { MatcherAssert.assertThat(shapeId + " shape must be marked as synthetic clone", false); } else { MatcherAssert.assertThat(synthetic.get().getArchetype().get().toString(), Matchers.is(Matchers.oneOf(NAMESPACE + "#TestOperationRequest", NAMESPACE + "#TestOperationResponse"))); } }); } @Test public void testOperationWithExistingInputOutputWithConflitcs() { GoSettings settings = new GoSettings(); settings.setService(SERVICE.toShapeId()); StructureShape inputConflict = StructureShape.builder() .id(ShapeId.fromParts(NAMESPACE, "TestOperationInput")) .build(); StructureShape outputConflict = StructureShape.builder() .id(ShapeId.fromParts(NAMESPACE, "TestOperationOutput")) .build(); OperationShape op = OperationShape.builder() .id(ShapeId.fromParts(NAMESPACE, "TestOperation")) .build(); ServiceShape service = SERVICE.toBuilder() .addOperation(op.getId()) .build(); Model model = Model.builder() .addShapes(inputConflict, outputConflict, op, service) .build(); Model processedModel = AddOperationShapes.execute(model, service.getId()); ShapeId expInputRename = ShapeId.fromParts(CodegenUtils.getSyntheticTypeNamespace(), "TestOperationInput"); ShapeId expOutputRename = ShapeId.fromParts(CodegenUtils.getSyntheticTypeNamespace(), "TestOperationOutput"); ListUtils.of(inputConflict.getId(), outputConflict.getId()) .forEach(shapeId -> { Optional shape = processedModel.getShape(shapeId); MatcherAssert.assertThat(shapeId + " shape must be present in model", shape.isPresent(), Matchers.is(true)); if (shape.get().getTrait(Synthetic.class).isPresent()) { MatcherAssert.assertThat("shape must not be marked as synthetic clone", false); } }); ListUtils.of(expInputRename, expOutputRename) .forEach(shapeId -> { Optional shape = processedModel.getShape(shapeId); MatcherAssert.assertThat(shapeId + " shape must be present in model", shape.isPresent(), Matchers.is(true)); Optional opSynth = shape.get().getTrait(Synthetic.class); if (opSynth.isPresent()) { Synthetic synth = opSynth.get(); MatcherAssert.assertThat(shapeId + " shape must not have archetype", synth.getArchetype().isPresent(), Matchers.equalTo(false)); } else { MatcherAssert.assertThat(shapeId + " shape must be synthetic clone", false); } }); } } DocumentationConverterTest.java000066400000000000000000000143151463735525100377430ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegenpackage software.amazon.smithy.go.codegen; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; public class DocumentationConverterTest { private static final int DEFAULT_DOC_WRAP_LENGTH = 80; @ParameterizedTest @MethodSource("cases") void convertsDocs(String given, String expected) { assertThat(DocumentationConverter.convert(given, DEFAULT_DOC_WRAP_LENGTH), equalTo(expected)); } private static Stream cases() { return Stream.of( Arguments.of( "Testing 1 2 3", "Testing 1 2 3" ), Arguments.of( "a link", "[a link]\n\n[a link]: https://example.com" ), Arguments.of( "empty link", "empty link" ), Arguments.of( "

  • Testing 1 2 3
  • FooBar
", " - Testing 1 2 3\n - FooBar" ), Arguments.of( "
  • Testing 1 2 3
  • FooBar
", " - Testing 1 2 3\n - FooBar" ), Arguments.of( "
  • Testing 1 2 3
  • FooBar
", " - Testing 1 2 3\n - FooBar" ), Arguments.of( "
  • Testing 1 2 3

  • FooBar

", " - Testing 1 2 3\n\n - FooBar" ), Arguments.of( "
  • Testing: 1 2 3
  • FooBar
", " - Testing : 1 2 3\n - FooBar" ), Arguments.of( "
  • FOO Bar

  • Xyz ABC

", " - FOO Bar\n\n - Xyz ABC" ), Arguments.of( "
  • foo
  • \tbar
  • \nbaz
", " - foo\n - bar\n - baz" ), Arguments.of( "

Testing: 1 2 3

", "Testing : 1 2 3" ), Arguments.of( "
Testing
", " Testing" ), Arguments.of( "

Testing 1 2 3

", "Testing 1 2 3" ), Arguments.of( "CreateSecret " + "SecretListEntry SecretName " + "KmsKeyId", "CreateSecret SecretListEntry SecretName KmsKeyId" ), Arguments.of( "

Deletes the replication configuration from the bucket. For information about replication" + " configuration, see " + "" + "Cross-Region Replication (CRR) in the Amazon S3 Developer Guide.

", " Deletes the replication configuration from the bucket. For information about\n" + "replication configuration, see [Cross-Region Replication (CRR)]in the Amazon S3 Developer Guide.\n\n" + "[Cross-Region Replication (CRR)]: https://docs.aws.amazon.com/AmazonS3/latest/dev/crr.html" ), Arguments.of( "* foo\n* bar", " - foo\n - bar" ), Arguments.of( "[a link](https://example.com)", "[a link]\n\n[a link]: https://example.com" ), Arguments.of("", ""), Arguments.of("", ""), Arguments.of("# Foo\nbar", "Foo\n\nbar"), Arguments.of("

Foo

bar", "Foo\n\nbar"), Arguments.of("## Foo\nbar", "Foo\n\nbar"), Arguments.of("

Foo

bar", "Foo\n\nbar"), Arguments.of("### Foo\nbar", "Foo\n\nbar"), Arguments.of("

Foo

bar", "Foo\n\nbar"), Arguments.of("#### Foo\nbar", "Foo\n\nbar"), Arguments.of("

Foo

bar", "Foo\n\nbar"), Arguments.of("##### Foo\nbar", "Foo\n\nbar"), Arguments.of("
Foo
bar", "Foo\n\nbar"), Arguments.of("###### Foo\nbar", "Foo\n\nbar"), Arguments.of("
Foo
bar", "Foo\n\nbar"), Arguments.of("Inline `code`", "Inline code"), Arguments.of("```\ncode block\n ```", " code block"), Arguments.of("```java\ncode block\n ```", " code block"), Arguments.of("foo
bar", "foo\n\nbar"), Arguments.of("

foo

", "foo"), Arguments.of("

paragraph one

paragraph two

", "paragraph one\n\nparagraph two"), Arguments.of("someone", "someone") ); } } EnumShapeGeneratorTest.java000066400000000000000000000042531463735525100367760ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.logging.Logger; import org.junit.jupiter.api.Test; import software.amazon.smithy.build.MockManifest; import software.amazon.smithy.build.PluginContext; import software.amazon.smithy.go.codegen.GoCodegenPlugin; import software.amazon.smithy.model.Model; import static org.hamcrest.Matchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static software.amazon.smithy.go.codegen.TestUtils.buildMockPluginContext; import static software.amazon.smithy.go.codegen.TestUtils.loadSmithyModelFromResource; import static software.amazon.smithy.go.codegen.TestUtils.loadExpectedFileStringFromResource; public class EnumShapeGeneratorTest { private static final Logger LOGGER = Logger.getLogger(EnumShapeGeneratorTest.class.getName()); @Test public void testEnumShapeTest() { // Arrange Model model = loadSmithyModelFromResource("enum-shape-test"); MockManifest manifest = new MockManifest(); PluginContext context = buildMockPluginContext(model, manifest, "smithy.example#Example"); // Act (new GoCodegenPlugin()).execute(context); // Assert String actualSuitEnumShapeCode = manifest.getFileString("types/enums.go").get(); String expectedSuitEnumShapeCode = loadExpectedFileStringFromResource("enum-shape-test", "types/enums.go"); assertThat("enum shape actual generated code is equal to the expected generated code", actualSuitEnumShapeCode, is(expectedSuitEnumShapeCode)); } } GenerateStandaloneGoModuleTest.java000066400000000000000000000105051463735525100404360ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegenpackage software.amazon.smithy.go.codegen; import static software.amazon.smithy.go.codegen.GoWriter.goBlockTemplate; import static software.amazon.smithy.go.codegen.TestUtils.hasGoInstalled; import static software.amazon.smithy.go.codegen.TestUtils.makeGoModule; import static software.amazon.smithy.go.codegen.TestUtils.testGoModule; import java.nio.file.Files; import java.util.Map; import java.util.logging.Logger; import org.junit.jupiter.api.Test; import software.amazon.smithy.build.FileManifest; import software.amazon.smithy.utils.MapUtils; public class GenerateStandaloneGoModuleTest { private static final Logger LOGGER = Logger.getLogger(GenerateStandaloneGoModuleTest.class.getName()); @Test public void testGenerateGoModule() throws Exception { if (!hasGoInstalled()) { LOGGER.warning("Skipping testGenerateGoModule, go command cannot be executed."); return; } var testPath = Files.createTempDirectory(getClass().getName()); LOGGER.warning("generating test suites into " + testPath); // symbol provider not needed here since only useFileWriter is called var fileManifest = FileManifest.create(testPath); var writers = new GoDelegator(fileManifest, null); writers.useFileWriter("test-directory/package-name/gofile.go", "github.com/aws/smithy-go/internal/testmodule/packagename", (w) -> { w.writeGoTemplate(""" type $name:L struct { Bar $barType:T Baz *$bazType:T } $somethingElse:W """, MapUtils.of( "name", "Foo", "barType", SymbolUtils.createValueSymbolBuilder("string").build(), "bazType", SymbolUtils.createValueSymbolBuilder("Request", SmithyGoDependency.SMITHY_HTTP_TRANSPORT).build(), "somethingElse", generateSomethingElse() )); }); writers.useFileWriter("test-directory/package-name/gofile_test.go", "github.com/aws/smithy-go/internal/testmodule/packagename", (w) -> { Map commonArgs = MapUtils.of( "testingT", SymbolUtils.createValueSymbolBuilder("T", SmithyGoDependency.TESTING).build() ); w.writeGoTemplate(""" func Test$name:L(t *$testingT:T) { v := $name:L{} v.Baz = nil } """, commonArgs, MapUtils.of( "name", "Foo" )); w.writeGoBlockTemplate("func TestBar(t *$testingT:T) {", "}", commonArgs, (ww) -> { ww.write("t.Skip(\"not relevant\")"); }); }); var dependencies = writers.getDependencies(); writers.flushWriters(); var goModuleInfo = new GoModuleInfo.Builder() .goDirective(GoModuleInfo.DEFAULT_GO_DIRECTIVE) .dependencies(dependencies) .build(); ManifestWriter.builder() .moduleName("github.com/aws/smithy-go/internal/testmodule") .fileManifest(fileManifest) .goModuleInfo(goModuleInfo) .build() .writeManifest(); makeGoModule(testPath); testGoModule(testPath); } private GoWriter.Writable generateSomethingElse() { return goBlockTemplate("func (s *$name:L) $funcName:L(i int) string {", "}", MapUtils.of("funcName", "SomethingElse"), MapUtils.of( "name", "Foo" ), (w) -> { w.write("return \"hello!\""); }); } } GoDependencyTest.java000066400000000000000000000154131463735525100356060ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import static org.hamcrest.MatcherAssert.assertThat; import java.util.List; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import software.amazon.smithy.codegen.core.SymbolDependency; public class GoDependencyTest { @Test public void testStandardLibraryDependency() { GoDependency dependency = GoDependency.builder() .type(GoDependency.Type.STANDARD_LIBRARY) .importPath("net/http") .version("1.14") .build(); List symbolDependencies = dependency.getDependencies(); assertThat(symbolDependencies.size(), Matchers.equalTo(1)); SymbolDependency symbolDependency = symbolDependencies.get(0); assertThat(symbolDependency.getDependencyType(), Matchers.equalTo("stdlib")); assertThat(symbolDependency.getPackageName(), Matchers.equalTo("")); assertThat(symbolDependency.getVersion(), Matchers.equalTo("1.14")); } @Test public void testSingleDependency() { GoDependency dependency = GoDependency.builder() .type(GoDependency.Type.DEPENDENCY) .sourcePath("github.com/aws/smithy-go") .importPath("github.com/aws/smithy-go/middleware") .version("1.2.3") .build(); List symbolDependencies = dependency.getDependencies(); assertThat(symbolDependencies.size(), Matchers.equalTo(1)); SymbolDependency symbolDependency = symbolDependencies.get(0); assertThat(symbolDependency.getDependencyType(), Matchers.equalTo("dependency")); assertThat(symbolDependency.getPackageName(), Matchers.equalTo("github.com/aws/smithy-go")); assertThat(symbolDependency.getVersion(), Matchers.equalTo("1.2.3")); } @Test public void testDependencyWithDependencies() { GoDependency dependency = GoDependency.builder() .type(GoDependency.Type.DEPENDENCY) .sourcePath("github.com/aws/aws-sdk-go-v2") .importPath("github.com/aws/aws-sdk-go-v2/aws/middleware") .version("1.2.3") .addDependency(GoDependency.builder() .type(GoDependency.Type.DEPENDENCY) .sourcePath("github.com/aws/smithy-go") .importPath("github.com/aws/smithy-go/middleware") .version("3.4.5") .build()) .build(); List symbolDependencies = dependency.getDependencies(); assertThat(symbolDependencies.size(), Matchers.equalTo(2)); assertThat(symbolDependencies, Matchers.containsInAnyOrder( Matchers.equalTo(SymbolDependency.builder() .dependencyType("dependency") .packageName("github.com/aws/aws-sdk-go-v2") .version("1.2.3") .build()), Matchers.equalTo(SymbolDependency.builder() .dependencyType("dependency") .packageName("github.com/aws/smithy-go") .version("3.4.5") .build()) )); } @Test public void testDependencyWithNestedDependencies() { GoDependency dependency = GoDependency.builder() .type(GoDependency.Type.DEPENDENCY) .sourcePath("github.com/aws/aws-sdk-go-v2") .importPath("github.com/aws/aws-sdk-go-v2/aws/middleware") .version("1.2.3") .addDependency(GoDependency.builder() .type(GoDependency.Type.DEPENDENCY) .sourcePath("github.com/aws/smithy-go") .importPath("github.com/aws/smithy-go/middleware") .version("3.4.5") .addDependency(GoDependency.builder() .type(GoDependency.Type.DEPENDENCY) .sourcePath("github.com/awslabs/smithy-go-extensions") .importPath("github.com/awslabs/smithy-go-extensions/foobar") .version("6.7.8") .addDependency(GoDependency.builder() .type(GoDependency.Type.STANDARD_LIBRARY) .importPath("net/http") .version("1.14") .build()) .addDependency(GoDependency.builder() .type(GoDependency.Type.STANDARD_LIBRARY) .importPath("time") .version("1.14") .build()) .build()) .build()) .build(); List symbolDependencies = dependency.getDependencies(); assertThat(symbolDependencies.size(), Matchers.equalTo(4)); assertThat(symbolDependencies, Matchers.containsInAnyOrder( Matchers.equalTo(SymbolDependency.builder() .dependencyType("dependency") .packageName("github.com/aws/aws-sdk-go-v2") .version("1.2.3") .build()), Matchers.equalTo(SymbolDependency.builder() .dependencyType("dependency") .packageName("github.com/aws/smithy-go") .version("3.4.5") .build()), Matchers.equalTo(SymbolDependency.builder() .dependencyType("dependency") .packageName("github.com/awslabs/smithy-go-extensions") .version("6.7.8") .build()), Matchers.equalTo(SymbolDependency.builder() .dependencyType("stdlib") .packageName("") .version("1.14") .build()) )); } } GoJmespathExpressionGeneratorTest.java000066400000000000000000000130461463735525100412320ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/* * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import static org.hamcrest.MatcherAssert.assertThat; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import software.amazon.smithy.go.codegen.util.ShapeUtil; import software.amazon.smithy.jmespath.JmespathExpression; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.ShapeId; import java.util.Map; public class GoJmespathExpressionGeneratorTest { private static final String TEST_MODEL_STR = """ $version: "2.0" namespace smithy.go.test structure Struct { simpleShape: String objectList: ObjectList objectMap: ObjectMap nested: NestedStruct } structure Object { key: String } structure NestedStruct { nestedField: String } list ObjectList { member: Object } map ObjectMap { key: String, value: Object } """; private static final Model TEST_MODEL = Model.assembler() .addUnparsedModel("model.smithy", TEST_MODEL_STR) .assemble().unwrap(); private static final GoSettings TEST_SETTINGS = GoSettings.from(ObjectNode.fromStringMap(Map.of( "service", "smithy.go.test#foo", "module", "github.com/aws/aws-sdk-go-v2/test" ))); private static GoCodegenContext testContext() { return new GoCodegenContext( TEST_MODEL, TEST_SETTINGS, new SymbolVisitor(TEST_MODEL, TEST_SETTINGS), null, null, null ); } @Test public void testFieldExpression() { var expr = "simpleShape"; var writer = new GoWriter("test"); var generator = new GoJmespathExpressionGenerator(testContext(), writer, TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")), JmespathExpression.parse(expr) ); var actual = generator.generate("input"); assertThat(actual.shape(), Matchers.equalTo(TEST_MODEL.expectShape(ShapeId.from("smithy.api#String")))); assertThat(actual.ident(), Matchers.equalTo("v2")); assertThat(writer.toString(), Matchers.containsString(""" v1 := input v2 := v1.SimpleShape """)); } @Test public void testSubexpression() { var expr = "nested.nestedField"; var writer = new GoWriter("test"); var generator = new GoJmespathExpressionGenerator(testContext(), writer, TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")), JmespathExpression.parse(expr) ); var actual = generator.generate("input"); assertThat(actual.shape(), Matchers.equalTo(TEST_MODEL.expectShape(ShapeId.from("smithy.api#String")))); assertThat(actual.ident(), Matchers.equalTo("v3")); assertThat(writer.toString(), Matchers.containsString(""" v1 := input v2 := v1.Nested v3 := v2.NestedField """)); } @Test public void testKeysFunctionExpression() { var expr = "keys(objectMap)"; var writer = new GoWriter("test"); var generator = new GoJmespathExpressionGenerator(testContext(), writer, TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")), JmespathExpression.parse(expr) ); var actual = generator.generate("input"); assertThat(actual.shape(), Matchers.equalTo(ShapeUtil.listOf(ShapeUtil.STRING_SHAPE))); assertThat(actual.ident(), Matchers.equalTo("v3")); assertThat(writer.toString(), Matchers.containsString(""" v1 := input v2 := v1.ObjectMap var v3 []string for k := range v2 { v3 = append(v3, k) } """)); } @Test public void testProjectionExpression() { var expr = "objectList[*].key"; var writer = new GoWriter("test"); var generator = new GoJmespathExpressionGenerator(testContext(), writer, TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")), JmespathExpression.parse(expr) ); var actual = generator.generate("input"); assertThat(actual.shape(), Matchers.equalTo( ShapeUtil.listOf(TEST_MODEL.expectShape(ShapeId.from("smithy.api#String"))))); assertThat(actual.ident(), Matchers.equalTo("v3")); assertThat(writer.toString(), Matchers.containsString(""" v1 := input v2 := v1.ObjectList var v3 []*string for _, v := range v2 { v1 := v v2 := v1.Key v3 = append(v3, v2) } """)); } } GoStackStepMiddlewareGeneratorTest.java000066400000000000000000000025771463735525100413050ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegenpackage software.amazon.smithy.go.codegen; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import org.junit.jupiter.api.Test; public class GoStackStepMiddlewareGeneratorTest { @Test public void generatesSerializeMiddlewareDefinition() { GoWriter writer = new GoWriter("middlewaregentest"); GoStackStepMiddlewareGenerator.createSerializeStepMiddleware("someMiddleware", MiddlewareIdentifier.string("some id")) .writeMiddleware(writer, (m, w) -> { w.openBlock("return next.$L(ctx, in)", m.getHandleMethodName()); }); String generated = writer.toString(); System.out.println(generated); assertThat(generated, containsString("type someMiddleware struct {")); assertThat(generated, containsString("func (*someMiddleware) ID() string {")); assertThat(generated, containsString("return \"some id\"")); assertThat(generated, containsString("func (m *someMiddleware) HandleSerialize(" + "ctx context.Context, in middleware.SerializeInput, next middleware.SerializeHandler) (")); assertThat(generated, containsString("out middleware.SerializeOutput, metadata middleware.Metadata, err error,")); assertThat(generated, containsString("return next.HandleSerialize(ctx, in)")); } } IntEnumShapeGeneratorTest.java000066400000000000000000000057701463735525100374560ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.logging.Logger; import org.junit.jupiter.api.Test; import software.amazon.smithy.build.MockManifest; import software.amazon.smithy.build.PluginContext; import software.amazon.smithy.go.codegen.GoCodegenPlugin; import software.amazon.smithy.model.Model; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static software.amazon.smithy.go.codegen.TestUtils.buildMockPluginContext; import static software.amazon.smithy.go.codegen.TestUtils.loadSmithyModelFromResource; import static software.amazon.smithy.go.codegen.TestUtils.loadExpectedFileStringFromResource; public class IntEnumShapeGeneratorTest { private static final Logger LOGGER = Logger.getLogger(IntEnumShapeGeneratorTest.class.getName()); @Test public void testIntEnumShapeTest() { // Arrange Model model = loadSmithyModelFromResource("int-enum-shape-test"); MockManifest manifest = new MockManifest(); PluginContext context = buildMockPluginContext(model, manifest, "smithy.example#Example"); // Act (new GoCodegenPlugin()).execute(context); // Assert String actualEnumShapeCode = manifest.getFileString("types/enums.go").get(); String expectedEnumShapeCode = loadExpectedFileStringFromResource("int-enum-shape-test", "types/enums.go"); assertThat("intEnum shape actual generated code is equal to the expected generated code", actualEnumShapeCode, is(expectedEnumShapeCode)); String actualChangeCardOperationCode = manifest.getFileString("api_op_ChangeCard.go").get(); String expectedChangeCardInputCode = loadExpectedFileStringFromResource("int-enum-shape-test", "changeCardInput.go.struct"); assertThat("intEnum shape properly referenced in generated input structure code", actualChangeCardOperationCode, containsString(expectedChangeCardInputCode)); String expectedChangeCardOutputCode = loadExpectedFileStringFromResource("int-enum-shape-test", "changeCardOutput.go.struct"); assertThat("intEnum shape properly referenced in generated output structure code", actualChangeCardOperationCode, containsString(expectedChangeCardOutputCode)); } } MixinCodegenTest.java000066400000000000000000000047331463735525100356160ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import java.util.logging.Logger; import org.junit.jupiter.api.Test; import software.amazon.smithy.build.MockManifest; import software.amazon.smithy.build.PluginContext; import software.amazon.smithy.go.codegen.GoCodegenPlugin; import software.amazon.smithy.model.Model; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static software.amazon.smithy.go.codegen.TestUtils.buildMockPluginContext; import static software.amazon.smithy.go.codegen.TestUtils.loadSmithyModelFromResource; import static software.amazon.smithy.go.codegen.TestUtils.loadExpectedFileStringFromResource; public class MixinCodegenTest { private static final Logger LOGGER = Logger.getLogger(MixinCodegenTest.class.getName()); @Test public void testMixinCodegen() { // Arrange Model model = loadSmithyModelFromResource("mixin-test"); MockManifest manifest = new MockManifest(); PluginContext context = buildMockPluginContext(model, manifest, "smithy.example#Example"); // Act (new GoCodegenPlugin()).execute(context); // Assert String actualChangeCardOperationCode = manifest.getFileString("api_op_ChangeCard.go").get(); String expectedInputMixinCode = loadExpectedFileStringFromResource("mixin-test", "changeCardInput.go.mixin"); assertThat("mixins are properly applied in the input structure", actualChangeCardOperationCode, containsString(expectedInputMixinCode)); String expectedOutputMixinCode = loadExpectedFileStringFromResource("mixin-test", "changeCardOutput.go.mixin"); assertThat("mixins are properly applied in the output structure", actualChangeCardOperationCode, containsString(expectedOutputMixinCode)); } } SemanticVersionTest.java000066400000000000000000000132051463735525100363500ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/* * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import static org.hamcrest.MatcherAssert.assertThat; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; public class SemanticVersionTest { @Test public void testSemanticVersion() { SemanticVersion version = SemanticVersion.parseVersion("1.2.3"); assertThat(version.toString(), Matchers.equalTo("1.2.3")); } @Test public void testSemanticVersionWithPrefix() { SemanticVersion version = SemanticVersion.parseVersion("v1.2.3"); assertThat(version.toString(), Matchers.equalTo("v1.2.3")); } @Test public void testSemanticVersionWithPreRelease() { SemanticVersion version = SemanticVersion.parseVersion("1.2.3-alpha"); assertThat(version.toString(), Matchers.equalTo("1.2.3-alpha")); } @Test public void testSemanticVersionWithBuild() { SemanticVersion version = SemanticVersion.parseVersion("1.2.3+1234"); assertThat(version.toString(), Matchers.equalTo("1.2.3+1234")); } @Test public void testSemanticVersionWithPreReleaseBuild() { SemanticVersion version = SemanticVersion.parseVersion("1.2.3-alpha+1234"); assertThat(version.toString(), Matchers.equalTo("1.2.3-alpha+1234")); } @Test public void testSemanticVersionWithPrefixPreReleaseBuild() { SemanticVersion version = SemanticVersion.parseVersion("v1.2.3-alpha+1234"); assertThat(version.toString(), Matchers.equalTo("v1.2.3-alpha+1234")); } @Test public void testCompareTo() { assertThat(SemanticVersion.parseVersion("v1.0.0").compareTo( SemanticVersion.parseVersion("v2.0.0")), Matchers.lessThan(0)); assertThat(SemanticVersion.parseVersion("v2.0.0").compareTo( SemanticVersion.parseVersion("v2.1.0")), Matchers.lessThan(0)); assertThat(SemanticVersion.parseVersion("v2.1.0").compareTo( SemanticVersion.parseVersion("v2.1.1")), Matchers.lessThan(0)); assertThat(SemanticVersion.parseVersion("v1.2.3").compareTo( SemanticVersion.parseVersion("v1.2.3")), Matchers.equalTo(0)); // Build metadata is ignored assertThat(SemanticVersion.parseVersion("v1.2.3-alpha+102030").compareTo( SemanticVersion.parseVersion("v1.2.3-alpha+405060")), Matchers.equalTo(0)); // 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0 assertThat(SemanticVersion.parseVersion("1.0.0-alpha").compareTo( SemanticVersion.parseVersion("1.0.0-alpha.1")), Matchers.lessThan(0)); assertThat(SemanticVersion.parseVersion("1.0.0-alpha.1").compareTo( SemanticVersion.parseVersion("1.0.0-alpha.beta")), Matchers.lessThan(0)); assertThat(SemanticVersion.parseVersion("1.0.0-alpha.beta").compareTo( SemanticVersion.parseVersion("1.0.0-beta")), Matchers.lessThan(0)); assertThat(SemanticVersion.parseVersion("1.0.0-beta").compareTo( SemanticVersion.parseVersion("1.0.0-beta.2")), Matchers.lessThan(0)); assertThat(SemanticVersion.parseVersion("1.0.0-beta.2").compareTo( SemanticVersion.parseVersion("1.0.0-beta.11")), Matchers.lessThan(0)); assertThat(SemanticVersion.parseVersion("1.0.0-beta.11").compareTo( SemanticVersion.parseVersion("1.0.0-rc.1")), Matchers.lessThan(0)); assertThat(SemanticVersion.parseVersion("1.0.0-rc.1").compareTo( SemanticVersion.parseVersion("1.0.0")), Matchers.lessThan(0)); // Reversed direction assertThat(SemanticVersion.parseVersion("1.0.0-alpha.1").compareTo( SemanticVersion.parseVersion("1.0.0-alpha")), Matchers.greaterThan(0)); assertThat(SemanticVersion.parseVersion("1.0.0-beta").compareTo( SemanticVersion.parseVersion("1.0.0-alpha.alpha.1")), Matchers.greaterThan(0)); assertThat(SemanticVersion.parseVersion("1.0.0-beta").compareTo( SemanticVersion.parseVersion("1.0.0-alpha.beta")), Matchers.greaterThan(0)); assertThat(SemanticVersion.parseVersion("1.0.0-beta.2").compareTo( SemanticVersion.parseVersion("1.0.0-beta")), Matchers.greaterThan(0)); assertThat(SemanticVersion.parseVersion("1.0.0-beta.11").compareTo( SemanticVersion.parseVersion("1.0.0-beta.2")), Matchers.greaterThan(0)); assertThat(SemanticVersion.parseVersion("1.0.0-rc.1").compareTo( SemanticVersion.parseVersion("1.0.0-beta.11")), Matchers.greaterThan(0)); assertThat(SemanticVersion.parseVersion("1.0.0").compareTo( SemanticVersion.parseVersion("1.0.0-rc.1")), Matchers.greaterThan(0)); } @Test public void testCompareToWithGoPseudoVersions() { assertThat(SemanticVersion.parseVersion("v1.2.3-20200518203908-8018eb2c26ba").compareTo( SemanticVersion.parseVersion("v1.2.3-20191204190536-9bdfabe68543")), Matchers.greaterThan(0)); } } TestUtils.java000066400000000000000000000127671463735525100343530ustar00rootroot00000000000000smithy-go-1.20.3/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/* * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.smithy.go.codegen; import static software.amazon.smithy.go.codegen.testutils.ExecuteCommand.execute; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import software.amazon.smithy.build.FileManifest; import software.amazon.smithy.build.PluginContext; import software.amazon.smithy.go.codegen.testutils.ExecuteCommand; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; public class TestUtils { public static final String SMITHY_TESTS_PREFIX = "smithy-tests"; public static final String SMITHY_TESTS_EXPECTED_PREFIX = "expected"; public static Model loadSmithyModelFromResource(String testPath) { String resourcePath = SMITHY_TESTS_PREFIX + "/" + testPath + "/" + testPath + ".smithy"; return Model.assembler() .addImport(TestUtils.class.getResource(resourcePath)) .discoverModels() .assemble() .unwrap(); } public static String loadExpectedFileStringFromResource(String testPath, String filePath) { String resourcePath = SMITHY_TESTS_PREFIX + "/" + testPath + "/" + SMITHY_TESTS_EXPECTED_PREFIX + "/" + filePath; return getResourceAsString(resourcePath); } public static String getResourceAsString(String resourcePath) { try { return Files.readString( Paths.get(TestUtils.class.getResource(resourcePath).toURI()), Charset.forName("utf-8")); } catch (Exception e) { return null; } } public static PluginContext buildMockPluginContext( Model model, FileManifest manifest, String serviceShapeId ) { return buildPluginContext( model, manifest, serviceShapeId, "example", "0.0.1", false); } public static PluginContext buildPluginContext( Model model, FileManifest manifest, String serviceShapeId, String moduleName, String moduleVersion, Boolean generateGoMod ) { return PluginContext.builder() .model(model) .fileManifest(manifest) .settings(getSettingsNode( serviceShapeId, moduleName, moduleVersion, generateGoMod, "Example")) .build(); } public static ObjectNode getSettingsNode( String serviceShapeId, String moduleName, String moduleVersion, Boolean generateGoMod, String sdkId ) { return Node.objectNodeBuilder() .withMember("service", Node.from(serviceShapeId)) .withMember("module", Node.from(moduleName)) .withMember("moduleVersion", Node.from(moduleVersion)) .withMember("generateGoMod", Node.from(generateGoMod)) .withMember("homepage", Node.from("https://docs.amplify.aws/")) .withMember("sdkId", Node.from(sdkId)) .withMember("author", Node.from("Amazon Web Services")) .withMember("gitRepo", Node.from("https://github.com/aws-amplify/amplify-codegen.git")) .withMember("swiftVersion", Node.from("5.5.0")) .build(); } /** * Returns the path for the repository's root relative to the smithy-go-codegen module. * @return repo root. */ public static Path getRepoRootDir() { return Path.of(System.getProperty("user.dir")) .resolve("..") .resolve("..") .toAbsolutePath(); } /** * Returns true if the go command can be executed. * @return go command can be executed. */ public static boolean hasGoInstalled() { try{ ExecuteCommand.builder() .addCommand("go", "version") .build() .execute(); } catch (Exception e) { return false; } return true; } public static void makeGoModule(Path path) throws Exception { var repoRoot = getRepoRootDir(); execute(repoRoot.toFile(), "go", "run", "github.com/awslabs/aws-go-multi-module-repository-tools/cmd/gomodgen@latest", "--build", path.resolve("..").toAbsolutePath().toString(), "--plugin-dir", path.getFileName().toString(), "--copy-artifact=false", "--prepare-target-dir=false" ); execute(path.toFile(), "go", "mod", "edit", "--replace", "github.com/aws/smithy-go="+repoRoot); execute(path.toFile(), "go", "mod", "tidy"); execute(path.toFile(), "gofmt", "-w", "-s", "."); } public static void testGoModule(Path path) throws Exception { execute(path.toFile(), "go", "test", "-v", "./..."); } } smithy-go-1.20.3/container/000077500000000000000000000000001463735525100155125ustar00rootroot00000000000000smithy-go-1.20.3/container/private/000077500000000000000000000000001463735525100171645ustar00rootroot00000000000000smithy-go-1.20.3/container/private/cache/000077500000000000000000000000001463735525100202275ustar00rootroot00000000000000smithy-go-1.20.3/container/private/cache/cache.go000066400000000000000000000013451463735525100216240ustar00rootroot00000000000000// Package cache defines the interface for a key-based data store. // // This package is designated as private and is intended for use only by the // smithy client runtime. The exported API therein is not considered stable and // is subject to breaking changes without notice. package cache // Cache defines the interface for an opaquely-typed, key-based data store. // // The thread-safety of this interface is undefined and is dictated by // implementations. type Cache interface { // Retrieve the value associated with the given key. The returned boolean // indicates whether the cache held a value for the given key. Get(k interface{}) (interface{}, bool) // Store a value under the given key. Put(k interface{}, v interface{}) } smithy-go-1.20.3/container/private/cache/lru/000077500000000000000000000000001463735525100210315ustar00rootroot00000000000000smithy-go-1.20.3/container/private/cache/lru/lru.go000066400000000000000000000023671463735525100221720ustar00rootroot00000000000000// Package lru implements [cache.Cache] with an LRU eviction policy. // // This implementation is NOT thread-safe. // // This package is designated as private and is intended for use only by the // smithy client runtime. The exported API therein is not considered stable and // is subject to breaking changes without notice. package lru import ( "container/list" "github.com/aws/smithy-go/container/private/cache" ) // New creates a new LRU cache with the given capacity. func New(cap int) cache.Cache { return &lru{ entries: make(map[interface{}]*list.Element, cap), cap: cap, mru: list.New(), } } type lru struct { entries map[interface{}]*list.Element cap int mru *list.List // least-recently used is at the back } type element struct { key interface{} value interface{} } func (l *lru) Get(k interface{}) (interface{}, bool) { e, ok := l.entries[k] if !ok { return nil, false } l.mru.MoveToFront(e) return e.Value.(*element).value, true } func (l *lru) Put(k interface{}, v interface{}) { if len(l.entries) == l.cap { l.evict() } ev := &element{ key: k, value: v, } e := l.mru.PushFront(ev) l.entries[k] = e } func (l *lru) evict() { e := l.mru.Remove(l.mru.Back()) delete(l.entries, e.(*element).key) } smithy-go-1.20.3/container/private/cache/lru/lru_test.go000066400000000000000000000031321463735525100232200ustar00rootroot00000000000000package lru import "testing" func TestCache(t *testing.T) { cache := New(4).(*lru) // fill cache cache.Put(1, 2) cache.Put(2, 3) cache.Put(3, 4) cache.Put(4, 5) assertEntry(t, cache, 1, 2) assertEntry(t, cache, 2, 3) assertEntry(t, cache, 3, 4) assertEntry(t, cache, 4, 5) // touch the last entry cache.Get(1) cache.Put(5, 6) assertNoEntry(t, cache, 2) assertEntry(t, cache, 3, 4) assertEntry(t, cache, 4, 5) assertEntry(t, cache, 1, 2) assertEntry(t, cache, 5, 6) // put something new, 3 should now be the oldest cache.Put(6, 7) assertNoEntry(t, cache, 3) assertEntry(t, cache, 4, 5) assertEntry(t, cache, 1, 2) assertEntry(t, cache, 5, 6) assertEntry(t, cache, 6, 7) // touch something in the middle cache.Get(5) assertEntry(t, cache, 4, 5) assertEntry(t, cache, 1, 2) assertEntry(t, cache, 5, 6) assertEntry(t, cache, 6, 7) // put 3 new things, 5 should remain after the touch cache.Put(7, 8) cache.Put(8, 9) cache.Put(9, 0) assertNoEntry(t, cache, 4) assertNoEntry(t, cache, 1) assertNoEntry(t, cache, 6) assertEntry(t, cache, 5, 6) assertEntry(t, cache, 7, 8) assertEntry(t, cache, 8, 9) assertEntry(t, cache, 9, 0) } func assertEntry(t *testing.T, c *lru, k interface{}, v interface{}) { e, ok := c.entries[k] if !ok { t.Errorf("expected entry %v=%v, but no entry found", k, v) } if actual := e.Value.(*element).value; actual != v { t.Errorf("expected entry %v=%v, but got entry value %v", k, v, actual) } } func assertNoEntry(t *testing.T, c *lru, k interface{}) { if _, ok := c.Get(k); ok { t.Errorf("expected no entry for %v, but one was found", k) } } smithy-go-1.20.3/context/000077500000000000000000000000001463735525100152145ustar00rootroot00000000000000smithy-go-1.20.3/context/suppress_expired.go000066400000000000000000000054621463735525100211560ustar00rootroot00000000000000package context import "context" // valueOnlyContext provides a utility to preserve only the values of a // Context. Suppressing any cancellation or deadline on that context being // propagated downstream of this value. // // If preserveExpiredValues is false (default), and the valueCtx is canceled, // calls to lookup values with the Values method, will always return nil. Setting // preserveExpiredValues to true, will allow the valueOnlyContext to lookup // values in valueCtx even if valueCtx is canceled. // // Based on the Go standard libraries net/lookup.go onlyValuesCtx utility. // https://github.com/golang/go/blob/da2773fe3e2f6106634673a38dc3a6eb875fe7d8/src/net/lookup.go type valueOnlyContext struct { context.Context preserveExpiredValues bool valuesCtx context.Context } var _ context.Context = (*valueOnlyContext)(nil) // Value looks up the key, returning its value. If configured to not preserve // values of expired context, and the wrapping context is canceled, nil will be // returned. func (v *valueOnlyContext) Value(key interface{}) interface{} { if !v.preserveExpiredValues { select { case <-v.valuesCtx.Done(): return nil default: } } return v.valuesCtx.Value(key) } // WithSuppressCancel wraps the Context value, suppressing its deadline and // cancellation events being propagated downstream to consumer of the returned // context. // // By default the wrapped Context's Values are available downstream until the // wrapped Context is canceled. Once the wrapped Context is canceled, Values // method called on the context return will no longer lookup any key. As they // are now considered expired. // // To override this behavior, use WithPreserveExpiredValues on the Context // before it is wrapped by WithSuppressCancel. This will make the Context // returned by WithSuppressCancel allow lookup of expired values. func WithSuppressCancel(ctx context.Context) context.Context { return &valueOnlyContext{ Context: context.Background(), valuesCtx: ctx, preserveExpiredValues: GetPreserveExpiredValues(ctx), } } type preserveExpiredValuesKey struct{} // WithPreserveExpiredValues adds a Value to the Context if expired values // should be preserved, and looked up by a Context wrapped by // WithSuppressCancel. // // WithPreserveExpiredValues must be added as a value to a Context, before that // Context is wrapped by WithSuppressCancel func WithPreserveExpiredValues(ctx context.Context, enable bool) context.Context { return context.WithValue(ctx, preserveExpiredValuesKey{}, enable) } // GetPreserveExpiredValues looks up, and returns the PreserveExpressValues // value in the context. Returning true if enabled, false otherwise. func GetPreserveExpiredValues(ctx context.Context) bool { v := ctx.Value(preserveExpiredValuesKey{}) if v != nil { return v.(bool) } return false } smithy-go-1.20.3/doc.go000066400000000000000000000001201463735525100146150ustar00rootroot00000000000000// Package smithy provides the core components for a Smithy SDK. package smithy smithy-go-1.20.3/document.go000066400000000000000000000004151463735525100156750ustar00rootroot00000000000000package smithy // Document provides access to loosely structured data in a document-like // format. // // Deprecated: See the github.com/aws/smithy-go/document package. type Document interface { UnmarshalDocument(interface{}) error GetValue() (interface{}, error) } smithy-go-1.20.3/document/000077500000000000000000000000001463735525100153465ustar00rootroot00000000000000smithy-go-1.20.3/document/cbor/000077500000000000000000000000001463735525100162735ustar00rootroot00000000000000smithy-go-1.20.3/document/cbor/cbor.go000066400000000000000000000005331463735525100175500ustar00rootroot00000000000000// Package cbor implements reflective encoding of Smithy documents for // CBOR-based protocols. // // This package is NOT caller-facing and is not suitable for general // application use. Callers using the document type with SDK clients should use // the embedded NewLazyDocument() API in the SDK package to create document // types. package cbor smithy-go-1.20.3/document/cbor/decode.go000066400000000000000000000211711463735525100200470ustar00rootroot00000000000000package cbor import ( "fmt" "math/big" "reflect" "github.com/aws/smithy-go/document" "github.com/aws/smithy-go/document/internal/serde" "github.com/aws/smithy-go/encoding/cbor" ) // decoderOptions is the set of options that can be configured for a Decoder. // // FUTURE(rpc2cbor): document support is currently disabled. This API is // unexported until that changes. type decoderOptions struct{} // decoder is a Smithy document decoder for CBOR-based protocols. // // FUTURE(rpc2cbor): document support is currently disabled. This API is // unexported until that changes. type decoder struct { options decoderOptions } // newDecoder returns a Decoder for deserializing Smithy documents. // // FUTURE(rpc2cbor): document support is currently disabled. This API is // unexported until that changes. func newDecoder(optFns ...func(options *decoderOptions)) *decoder { o := decoderOptions{} for _, fn := range optFns { fn(&o) } return &decoder{ options: o, } } // Decode unmarshals a CBOR Value into the target. func (d *decoder) Decode(v cbor.Value, to interface{}) error { if document.IsNoSerde(to) { return fmt.Errorf("unsupported type: %T", to) } rv := reflect.ValueOf(to) if rv.Kind() != reflect.Ptr || rv.IsNil() || !rv.IsValid() { return &document.InvalidUnmarshalError{reflect.TypeOf(to)} } return d.decode(v, rv, serde.Tag{}) } func (d *decoder) decode(cv cbor.Value, rv reflect.Value, tag serde.Tag) error { if _, ok := cv.(*cbor.Nil); ok { return d.decodeNil(serde.Indirect(rv, true)) } rv = serde.Indirect(rv, false) if err := d.unsupportedType(rv); err != nil { return err } switch v := cv.(type) { case cbor.Uint, cbor.NegInt: return d.decodeInt(v, rv) case cbor.Float64: return d.decodeFloat(float64(v), rv) case cbor.String: return d.decodeString(string(v), rv) case cbor.Bool: return d.decodeBool(bool(v), rv) case cbor.List: return d.decodeList(v, rv) case cbor.Map: return d.decodeMap(v, rv) case *cbor.Tag: return d.decodeTag(v, rv) default: return fmt.Errorf("unsupported cbor document type %T", v) } } func (d *decoder) decodeInt(v cbor.Value, rv reflect.Value) error { switch rv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i, err := cbor.AsInt64(v) if err != nil { return err } if rv.OverflowInt(i) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("number overflow, %d", i), Type: rv.Type(), } } rv.SetInt(i) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: u, ok := v.(cbor.Uint) if !ok { return &document.UnmarshalTypeError{Value: "number", Type: rv.Type()} } if rv.OverflowUint(uint64(u)) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("number overflow, %d", u), Type: rv.Type(), } } rv.SetUint(uint64(u)) default: return &document.UnmarshalTypeError{Value: "number", Type: rv.Type()} } return nil } func (d *decoder) decodeNil(rv reflect.Value) error { if rv.IsValid() && rv.CanSet() { rv.Set(reflect.Zero(rv.Type())) } return nil } func (d *decoder) decodeBool(v bool, rv reflect.Value) error { switch rv.Kind() { case reflect.Bool, reflect.Interface: rv.Set(reflect.ValueOf(v).Convert(rv.Type())) default: return &document.UnmarshalTypeError{Value: "bool", Type: rv.Type()} } return nil } func (d *decoder) decodeFloat(v float64, rv reflect.Value) error { switch rv.Kind() { case reflect.Interface: rv.Set(reflect.ValueOf(v)) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i, accuracy := big.NewFloat(v).Int64() if accuracy != big.Exact || rv.OverflowInt(i) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("int overflow, %e", v), Type: rv.Type(), } } rv.SetInt(i) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: u, accuracy := big.NewFloat(v).Uint64() if accuracy != big.Exact || rv.OverflowUint(u) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("uint overflow, %e", v), Type: rv.Type(), } } rv.SetUint(u) case reflect.Float32, reflect.Float64: if rv.OverflowFloat(v) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("float overflow, %e", v), Type: rv.Type(), } } rv.SetFloat(v) default: return &document.UnmarshalTypeError{Value: "number", Type: rv.Type()} } return nil } func (d *decoder) decodeList(v cbor.List, rv reflect.Value) error { var isArray bool switch rv.Kind() { case reflect.Slice: // Make room for the slice elements if needed if rv.IsNil() || rv.Cap() < len(v) { rv.Set(reflect.MakeSlice(rv.Type(), 0, len(v))) } case reflect.Array: // Limited to capacity of existing array. isArray = true case reflect.Interface: s := make([]interface{}, len(v)) for i, av := range v { if err := d.decode(av, reflect.ValueOf(&s[i]).Elem(), serde.Tag{}); err != nil { return err } } rv.Set(reflect.ValueOf(s)) return nil default: return &document.UnmarshalTypeError{Value: "list", Type: rv.Type()} } // If rv is not a slice, array for i := 0; i < rv.Cap() && i < len(v); i++ { if !isArray { rv.SetLen(i + 1) } if err := d.decode(v[i], rv.Index(i), serde.Tag{}); err != nil { return err } } return nil } func (d *decoder) decodeString(v string, rv reflect.Value) error { switch rv.Kind() { case reflect.String: rv.SetString(v) case reflect.Interface: rv.Set(reflect.ValueOf(v).Convert(rv.Type())) default: return &document.UnmarshalTypeError{Value: "string", Type: rv.Type()} } return nil } func (d *decoder) decodeMap(tv cbor.Map, rv reflect.Value) error { switch rv.Kind() { case reflect.Map: t := rv.Type() if t.Key().Kind() != reflect.String { return &document.UnmarshalTypeError{Value: "map string key", Type: t.Key()} } if rv.IsNil() { rv.Set(reflect.MakeMap(t)) } case reflect.Struct: if rv.CanInterface() && document.IsNoSerde(rv.Interface()) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("unsupported type"), Type: rv.Type(), } } case reflect.Interface: rv.Set(reflect.MakeMap(serde.ReflectTypeOf.MapStringToInterface)) rv = rv.Elem() default: return &document.UnmarshalTypeError{Value: "map", Type: rv.Type()} } if rv.Kind() == reflect.Map { for k, kv := range tv { key := reflect.New(rv.Type().Key()).Elem() key.SetString(k) elem := reflect.New(rv.Type().Elem()).Elem() if err := d.decode(kv, elem, serde.Tag{}); err != nil { return err } rv.SetMapIndex(key, elem) } } else if rv.Kind() == reflect.Struct { fields := serde.GetStructFields(rv.Type()) for k, kv := range tv { if f, ok := fields.FieldByName(k); ok { fv := serde.DecoderFieldByIndex(rv, f.Index) if err := d.decode(kv, fv, f.Tag); err != nil { return err } } } } return nil } func (d *decoder) decodeTag(tv *cbor.Tag, rv reflect.Value) error { rvt := rv.Type() switch { case rvt.ConvertibleTo(serde.ReflectTypeOf.BigInt): i, err := cbor.AsBigInt(tv) if err != nil { return &document.UnmarshalTypeError{Value: "tag", Type: rv.Type()} } rv.Set(reflect.ValueOf(*i).Convert(rvt)) return nil case rvt.ConvertibleTo(serde.ReflectTypeOf.BigFloat): i, err := asBigFloat(tv) if err != nil { return &document.UnmarshalTypeError{Value: "tag", Type: rv.Type()} } rv.Set(reflect.ValueOf(*i).Convert(rvt)) return nil default: return &document.UnmarshalTypeError{Value: "tag", Type: rv.Type()} } } func (d *decoder) unsupportedType(rv reflect.Value) error { if rv.Kind() == reflect.Interface && rv.NumMethod() != 0 { return &document.UnmarshalTypeError{Value: "non-empty interface", Type: rv.Type()} } if rv.Type().ConvertibleTo(serde.ReflectTypeOf.Time) { return &document.UnmarshalTypeError{ Type: rv.Type(), Value: fmt.Sprintf("time value"), } } return nil } func asBigFloat(tv *cbor.Tag) (*big.Float, error) { const tagbase10 = 4 if tv.ID != tagbase10 { return nil, fmt.Errorf("invalid tag: %d", tv.ID) } pcs, ok := tv.Value.(cbor.List) if !ok { return nil, fmt.Errorf("invalid tagged type: %T", tv.Value) } if len(pcs) != 2 { return nil, fmt.Errorf("invalid tagged list len: %d", len(pcs)) } eval, mval := pcs[0], pcs[1] exp, err := cbor.AsBigInt(eval) if err != nil { return nil, fmt.Errorf("invalid exp: %w", err) } mant, err := cbor.AsBigInt(mval) if !ok { return nil, fmt.Errorf("invalid mant: %w", err) } // We literally re-express this as e and send it through // bigfloat parse. Not mathematically amazing, but ensures that // string-borne bignums and this are computed identically. str := fmt.Sprintf("%se%s", mant.String(), exp.String()) x, _, err := new(big.Float).Parse(str, 0) return x, err } smithy-go-1.20.3/document/cbor/decode_test.go000066400000000000000000000044051463735525100211070ustar00rootroot00000000000000package cbor import ( "math" "math/big" "reflect" "testing" "github.com/aws/smithy-go/encoding/cbor" "github.com/aws/smithy-go/ptr" ) func TestDecode_KitchenSink(t *testing.T) { type target struct { Int8 int8 Int16 int16 Int32 int32 Int64 int64 Uint8 uint8 Uint16 uint16 Uint32 uint32 Uint64 uint64 Slice []byte String string List []target Map map[string]target UintptrNil *uint UintptrNonnil *uint Bool bool Float float64 BigInt *big.Int BigNegInt *big.Int BigFloat *big.Float } in := cbor.Map{ "Int8": cbor.NegInt(8), "Int16": cbor.NegInt(16), "Int32": cbor.NegInt(32), "Int64": cbor.NegInt(64), "Uint8": cbor.Uint(8), "Uint16": cbor.Uint(16), "Uint32": cbor.Uint(32), "Uint64": cbor.Uint(64), "String": cbor.String("foo"), "List": cbor.List{ cbor.Map{ "Int8": cbor.NegInt(8), }, }, "Map": cbor.Map{ "k0": cbor.Map{ "Int8": cbor.NegInt(8), }, }, "UintptrNil": &cbor.Nil{}, "UintptrNonnil": cbor.Uint(4), "Bool": cbor.Bool(true), "Float": cbor.Float64(math.Inf(1)), "BigInt": &cbor.Tag{ ID: 2, Value: cbor.Slice{1, 0, 0, 0, 0, 0, 0, 0, 0}, }, "BigNegInt": &cbor.Tag{ ID: 3, Value: cbor.Slice{1, 0, 0, 0, 0, 0, 0, 0, 0}, }, "BigFloat": &cbor.Tag{ ID: 4, Value: cbor.List{ cbor.NegInt(200), // exp cbor.Uint(200), // mant }, }, "UnknownField": &cbor.Nil{}, } expect := target{ Int8: -8, Int16: -16, Int32: -32, Int64: -64, Uint8: 8, Uint16: 16, Uint32: 32, Uint64: 64, String: "foo", List: []target{ {Int8: -8}, }, Map: map[string]target{ "k0": {Int8: -8}, }, UintptrNil: nil, UintptrNonnil: ptr.Uint(4), Bool: true, Float: math.Inf(1), BigInt: new(big.Int).SetBytes([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0}), BigNegInt: new(big.Int).Sub( big.NewInt(-1), new(big.Int).SetBytes([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0}), ), BigFloat: func() *big.Float { x, _ := new(big.Float).SetString("200e-200") return x }(), } var actual target dec := &decoder{} if err := dec.Decode(in, &actual); err != nil { t.Fatal(err) } if !reflect.DeepEqual(expect, actual) { t.Errorf("%v != %v", expect, actual) } } smithy-go-1.20.3/document/cbor/encode.go000066400000000000000000000132061463735525100200610ustar00rootroot00000000000000package cbor import ( "fmt" "math/big" "reflect" "github.com/aws/smithy-go/document" "github.com/aws/smithy-go/document/internal/serde" "github.com/aws/smithy-go/encoding/cbor" ) // encoderOptions is the set of options that can be configured for an Encoder. // // FUTURE(rpc2cbor): document support is currently disabled. This API is // unexported until that changes. type encoderOptions struct{} // encoder is a Smithy document encoder for CBOR-based protocols. // // FUTURE(rpc2cbor): document support is currently disabled. This API is // unexported until that changes. type encoder struct { options encoderOptions } // newEncoder returns an Encoder for serializing Smithy documents. // // FUTURE(rpc2cbor): document support is currently disabled. This API is // unexported until that changes. func newEncoder(optFns ...func(options *encoderOptions)) *encoder { o := encoderOptions{} for _, fn := range optFns { fn(&o) } return &encoder{ options: o, } } // Encode returns the CBOR encoding of v. func (e *encoder) Encode(v interface{}) ([]byte, error) { cv, err := e.encode(reflect.ValueOf(v), serde.Tag{}) if err != nil { return nil, err } return cbor.Encode(cv), nil } func (e *encoder) encode(rv reflect.Value, tag serde.Tag) (cbor.Value, error) { if serde.IsZeroValue(rv) { if tag.OmitEmpty { return nil, nil } return e.encodeZeroValue(rv) } rv = serde.ValueElem(rv) switch rv.Kind() { case reflect.Struct: return e.encodeStruct(rv) case reflect.Map: return e.encodeMap(rv) case reflect.Slice, reflect.Array: return e.encodeSlice(rv) case reflect.Invalid, reflect.Chan, reflect.Func, reflect.UnsafePointer: return nil, nil default: return e.encodeScalar(rv) } } func (e *encoder) encodeZeroValue(rv reflect.Value) (cbor.Value, error) { switch rv.Kind() { case reflect.Array: return cbor.List{}, nil case reflect.String: return cbor.String(""), nil case reflect.Bool: return cbor.Bool(false), nil case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return cbor.EncodeFixedUint(0), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return cbor.EncodeFixedUint(0), nil case reflect.Float32, reflect.Float64: return cbor.Float64(0), nil case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: return &cbor.Nil{}, nil default: return nil, &document.InvalidMarshalError{Message: fmt.Sprintf("unknown value type: %s", rv.String())} } } func (e *encoder) encodeStruct(rv reflect.Value) (cbor.Value, error) { if rv.CanInterface() && document.IsNoSerde(rv.Interface()) { return nil, &document.UnmarshalTypeError{ Value: fmt.Sprintf("unsupported type"), Type: rv.Type(), } } switch { case rv.Type().ConvertibleTo(serde.ReflectTypeOf.Time): return nil, &document.InvalidMarshalError{ Message: fmt.Sprintf("unsupported type %s", rv.Type().String()), } case rv.Type().ConvertibleTo(serde.ReflectTypeOf.BigFloat): fallthrough case rv.Type().ConvertibleTo(serde.ReflectTypeOf.BigInt): return e.encodeNumber(rv) } fields := serde.GetStructFields(rv.Type()) mv := cbor.Map{} for _, f := range fields.All() { if f.Name == "" { return nil, &document.InvalidMarshalError{Message: "map key cannot be empty"} } fv, found := serde.EncoderFieldByIndex(rv, f.Index) if !found { continue } cv, err := e.encode(fv, f.Tag) if err != nil { return nil, err } if cv == nil { // from omitEmpty continue } mv[f.Name] = cv } return mv, nil } func (e *encoder) encodeMap(rv reflect.Value) (cbor.Map, error) { mv := cbor.Map{} for _, key := range rv.MapKeys() { keyName := fmt.Sprint(key.Interface()) if keyName == "" { return nil, &document.InvalidMarshalError{"map key cannot be empty"} } cv, err := e.encode(rv.MapIndex(key), serde.Tag{}) if err != nil { return nil, err } mv[keyName] = cv } return mv, nil } func (e *encoder) encodeSlice(rv reflect.Value) (cbor.List, error) { lv := cbor.List{} for i := 0; i < rv.Len(); i++ { cv, err := e.encode(rv.Index(i), serde.Tag{}) if err != nil { return nil, err } lv = append(lv, cv) } return lv, nil } func (e *encoder) encodeScalar(rv reflect.Value) (cbor.Value, error) { switch rv.Kind() { case reflect.Bool: return cbor.Bool(rv.Bool()), nil case reflect.String: return cbor.String(rv.String()), nil default: return e.encodeNumber(rv) } } func (e *encoder) encodeNumber(rv reflect.Value) (cbor.Value, error) { const tagbigpos = 2 const tagbigneg = 3 const tagbigfloat = 4 switch rv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: iv := rv.Int() if iv >= 0 { return cbor.EncodeFixedUint(uint64(iv)), nil } return cbor.EncodeFixedNegInt(uint64(-iv)), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return cbor.EncodeFixedUint(rv.Uint()), nil case reflect.Float32, reflect.Float64: return cbor.Float64(rv.Float()), nil default: rvt := rv.Type() switch { case rvt.ConvertibleTo(serde.ReflectTypeOf.BigInt): i := rv.Convert(serde.ReflectTypeOf.BigInt).Interface().(big.Int) if i.Sign() > -1 { return &cbor.Tag{ ID: tagbigpos, Value: cbor.Slice(i.Bytes()), }, nil } biased := new(big.Int).Add(&i, big.NewInt(1)) return &cbor.Tag{ ID: tagbigneg, Value: cbor.Slice(biased.Bytes()), }, nil case rvt.ConvertibleTo(serde.ReflectTypeOf.BigFloat): // FUTURE(rpc2cbor): when document support is enabled, complete this logic return &cbor.Tag{}, nil default: return nil, &document.InvalidMarshalError{ Message: fmt.Sprintf("incompatible type: %s", rvt.String()), } } } } smithy-go-1.20.3/document/cbor/encode_test.go000066400000000000000000000043571463735525100211270ustar00rootroot00000000000000package cbor import ( "math" "math/big" "reflect" "testing" "github.com/aws/smithy-go/encoding/cbor" "github.com/aws/smithy-go/ptr" ) func TestEncode_KitchenSink(t *testing.T) { type subtarget struct { Int8 int8 Int16 int16 } type target struct { Int8 int8 Int16 int16 Int32 int32 Int64 int64 Uint8 uint8 Uint16 uint16 Uint32 uint32 Uint64 uint64 String string List []subtarget Map map[string]subtarget UintptrNil *uint UintptrNonnil *uint Bool bool Float float64 BigInt *big.Int BigNegInt *big.Int } in := target{ Int8: -8, Int16: -16, Int32: -32, Int64: -64, Uint8: 8, Uint16: 16, Uint32: 32, Uint64: 64, String: "foo", List: []subtarget{ {Int8: -8}, }, Map: map[string]subtarget{ "k0": {Int8: -8}, }, UintptrNil: nil, UintptrNonnil: ptr.Uint(4), Bool: true, Float: math.Inf(1), BigInt: new(big.Int).SetBytes([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0}), BigNegInt: new(big.Int).Sub( big.NewInt(-1), new(big.Int).SetBytes([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0}), ), } expect := cbor.Map{ "Int8": cbor.NegInt(8), "Int16": cbor.NegInt(16), "Int32": cbor.NegInt(32), "Int64": cbor.NegInt(64), "Uint8": cbor.Uint(8), "Uint16": cbor.Uint(16), "Uint32": cbor.Uint(32), "Uint64": cbor.Uint(64), "String": cbor.String("foo"), "List": cbor.List{ cbor.Map{ "Int8": cbor.NegInt(8), "Int16": cbor.Uint(0), // implicit }, }, "Map": cbor.Map{ "k0": cbor.Map{ "Int8": cbor.NegInt(8), "Int16": cbor.Uint(0), }, }, "UintptrNil": &cbor.Nil{}, "UintptrNonnil": cbor.Uint(4), "Bool": cbor.Bool(true), "Float": cbor.Float64(math.Inf(1)), "BigInt": &cbor.Tag{ ID: 2, Value: cbor.Slice{1, 0, 0, 0, 0, 0, 0, 0, 0}, }, "BigNegInt": &cbor.Tag{ ID: 3, Value: cbor.Slice{1, 0, 0, 0, 0, 0, 0, 0, 0}, }, } enc := &encoder{} encoded, err := enc.Encode(in) if err != nil { t.Fatal(err) } actual, err := cbor.Decode(encoded) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(expect, actual) { t.Errorf("%v != %v", expect, actual) } } smithy-go-1.20.3/document/doc.go000066400000000000000000000012731463735525100164450ustar00rootroot00000000000000// Package document provides interface definitions and error types for document types. // // A document is a protocol-agnostic type which supports a JSON-like data-model. You can use this type to send // UTF-8 strings, arbitrary precision numbers, booleans, nulls, a list of these values, and a map of UTF-8 // strings to these values. // // API Clients expose document constructors in their respective client document packages which must be used to // Marshal and Unmarshal Go types to and from their respective protocol representations. // // See the Marshaler and Unmarshaler type documentation for more details on how to Go types can be converted to and from // document types. package document smithy-go-1.20.3/document/document.go000066400000000000000000000123211463735525100175120ustar00rootroot00000000000000package document import ( "fmt" "math/big" "strconv" ) // Marshaler is an interface for a type that marshals a document to its protocol-specific byte representation and // returns the resulting bytes. A non-nil error will be returned if an error is encountered during marshaling. // // Marshal supports basic scalars (int,uint,float,bool,string), big.Int, and big.Float, maps, slices, and structs. // Anonymous nested types are flattened based on Go anonymous type visibility. // // When defining struct types. the `document` struct tag can be used to control how the value will be // marshaled into the resulting protocol document. // // // Field is ignored // Field int `document:"-"` // // // Field object of key "myName" // Field int `document:"myName"` // // // Field object key of key "myName", and // // Field is omitted if the field is a zero value for the type. // Field int `document:"myName,omitempty"` // // // Field object key of "Field", and // // Field is omitted if the field is a zero value for the type. // Field int `document:",omitempty"` // // All struct fields, including anonymous fields, are marshaled unless the // any of the following conditions are meet. // // - the field is not exported // - document field tag is "-" // - document field tag specifies "omitempty", and is a zero value. // // Pointer and interface values are encoded as the value pointed to or // contained in the interface. A nil value encodes as a null // value unless `omitempty` struct tag is provided. // // Channel, complex, and function values are not encoded and will be skipped // when walking the value to be marshaled. // // time.Time is not supported and will cause the Marshaler to return an error. These values should be represented // by your application as a string or numerical representation. // // Errors that occur when marshaling will stop the marshaler, and return the error. // // Marshal cannot represent cyclic data structures and will not handle them. // Passing cyclic structures to Marshal will result in an infinite recursion. type Marshaler interface { MarshalSmithyDocument() ([]byte, error) } // Unmarshaler is an interface for a type that unmarshals a document from its protocol-specific representation, and // stores the result into the value pointed by v. If v is nil or not a pointer then InvalidUnmarshalError will be // returned. // // Unmarshaler supports the same encodings produced by a document Marshaler. This includes support for the `document` // struct field tag for controlling how struct fields are unmarshaled. // // Both generic interface{} and concrete types are valid unmarshal destination types. When unmarshaling a document // into an empty interface the Unmarshaler will store one of these values: // bool, for boolean values // document.Number, for arbitrary-precision numbers (int64, float64, big.Int, big.Float) // string, for string values // []interface{}, for array values // map[string]interface{}, for objects // nil, for null values // // When unmarshaling, any error that occurs will halt the unmarshal and return the error. type Unmarshaler interface { UnmarshalSmithyDocument(v interface{}) error } type noSerde interface { noSmithyDocumentSerde() } // NoSerde is a sentinel value to indicate that a given type should not be marshaled or unmarshaled // into a protocol document. type NoSerde struct{} func (n NoSerde) noSmithyDocumentSerde() {} var _ noSerde = (*NoSerde)(nil) // IsNoSerde returns whether the given type implements the no smithy document serde interface. func IsNoSerde(x interface{}) bool { _, ok := x.(noSerde) return ok } // Number is an arbitrary precision numerical value type Number string // Int64 returns the number as a string. func (n Number) String() string { return string(n) } // Int64 returns the number as an int64. func (n Number) Int64() (int64, error) { return n.intOfBitSize(64) } func (n Number) intOfBitSize(bitSize int) (int64, error) { return strconv.ParseInt(string(n), 10, bitSize) } // Uint64 returns the number as a uint64. func (n Number) Uint64() (uint64, error) { return n.uintOfBitSize(64) } func (n Number) uintOfBitSize(bitSize int) (uint64, error) { return strconv.ParseUint(string(n), 10, bitSize) } // Float32 returns the number parsed as a 32-bit float, returns a float64. func (n Number) Float32() (float64, error) { return n.floatOfBitSize(32) } // Float64 returns the number as a float64. func (n Number) Float64() (float64, error) { return n.floatOfBitSize(64) } // Float64 returns the number as a float64. func (n Number) floatOfBitSize(bitSize int) (float64, error) { return strconv.ParseFloat(string(n), bitSize) } // BigFloat attempts to convert the number to a big.Float, returns an error if the operation fails. func (n Number) BigFloat() (*big.Float, error) { f, ok := (&big.Float{}).SetString(string(n)) if !ok { return nil, fmt.Errorf("failed to convert to big.Float") } return f, nil } // BigInt attempts to convert the number to a big.Int, returns an error if the operation fails. func (n Number) BigInt() (*big.Int, error) { f, ok := (&big.Int{}).SetString(string(n), 10) if !ok { return nil, fmt.Errorf("failed to convert to big.Float") } return f, nil } smithy-go-1.20.3/document/errors.go000066400000000000000000000043271463735525100172170ustar00rootroot00000000000000package document import ( "fmt" "reflect" ) // UnmarshalTypeError is an error type representing an error // unmarshaling a Smithy document to a Go value type. This is different // from UnmarshalError in that it does not wrap an underlying error type. type UnmarshalTypeError struct { Value string Type reflect.Type } // Error returns the string representation of the error. // Satisfying the error interface. func (e *UnmarshalTypeError) Error() string { return fmt.Sprintf("unmarshal failed, cannot unmarshal %s into Go value type %s", e.Value, e.Type.String()) } // An InvalidUnmarshalError is an error type representing an invalid type // encountered while unmarshaling a Smithy document to a Go value type. type InvalidUnmarshalError struct { Type reflect.Type } // Error returns the string representation of the error. // Satisfying the error interface. func (e *InvalidUnmarshalError) Error() string { var msg string if e.Type == nil { msg = "cannot unmarshal to nil value" } else if e.Type.Kind() != reflect.Ptr { msg = fmt.Sprintf("cannot unmarshal to non-pointer value, got %s", e.Type.String()) } else { msg = fmt.Sprintf("cannot unmarshal to nil value, %s", e.Type.String()) } return fmt.Sprintf("unmarshal failed, %s", msg) } // An UnmarshalError wraps an error that occurred while unmarshaling a // Smithy document into a Go type. This is different from // UnmarshalTypeError in that it wraps the underlying error that occurred. type UnmarshalError struct { Err error Value string Type reflect.Type } // Unwrap returns the underlying unmarshaling error func (e *UnmarshalError) Unwrap() error { return e.Err } // Error returns the string representation of the error. // Satisfying the error interface. func (e *UnmarshalError) Error() string { return fmt.Sprintf("unmarshal failed, cannot unmarshal %q into %s, %v", e.Value, e.Type.String(), e.Err) } // An InvalidMarshalError is an error type representing an error // occurring when marshaling a Go value type. type InvalidMarshalError struct { Message string } // Error returns the string representation of the error. // Satisfying the error interface. func (e *InvalidMarshalError) Error() string { return fmt.Sprintf("marshal failed, %s", e.Message) } smithy-go-1.20.3/document/internal/000077500000000000000000000000001463735525100171625ustar00rootroot00000000000000smithy-go-1.20.3/document/internal/serde/000077500000000000000000000000001463735525100202645ustar00rootroot00000000000000smithy-go-1.20.3/document/internal/serde/field.go000066400000000000000000000171311463735525100217010ustar00rootroot00000000000000package serde import ( "reflect" "sort" ) const tagKey = "document" // Field is represents a struct field, tag, type, and index. type Field struct { Tag Name string NameFromTag bool Index []int Type reflect.Type } func buildField(pIdx []int, i int, sf reflect.StructField, fieldTag Tag) Field { f := Field{ Name: sf.Name, Type: sf.Type, Tag: fieldTag, } if len(fieldTag.Name) != 0 { f.NameFromTag = true f.Name = fieldTag.Name } f.Index = make([]int, len(pIdx)+1) copy(f.Index, pIdx) f.Index[len(pIdx)] = i return f } // GetStructFields returns a list of fields for the given type. Type info is cached // to avoid repeated calls into the reflect package func GetStructFields(t reflect.Type) *CachedFields { if cached, ok := fieldCache.Load(t); ok { return cached } f := enumFields(t) sort.Sort(fieldsByName(f)) f = visibleFields(f) fs := &CachedFields{ fields: f, fieldsByName: make(map[string]int, len(f)), } for i, f := range fs.fields { fs.fieldsByName[f.Name] = i } cached, _ := fieldCache.LoadOrStore(t, fs) return cached } // enumFields will recursively iterate through a structure and its nested // anonymous fields. // // Based on the enoding/json struct field enumeration of the Go Stdlib // https://golang.org/src/encoding/json/encode.go typeField func. func enumFields(t reflect.Type) []Field { // Fields to explore current := []Field{} next := []Field{{Type: t}} // count of queued names count := map[reflect.Type]int{} nextCount := map[reflect.Type]int{} visited := map[reflect.Type]struct{}{} fields := []Field{} for len(next) > 0 { current, next = next, current[:0] count, nextCount = nextCount, map[reflect.Type]int{} for _, f := range current { if _, ok := visited[f.Type]; ok { continue } visited[f.Type] = struct{}{} for i := 0; i < f.Type.NumField(); i++ { sf := f.Type.Field(i) if sf.PkgPath != "" && !sf.Anonymous { // Ignore unexported and non-anonymous fields // unexported but anonymous field may still be used if // the type has exported nested fields continue } fieldTag := ParseTag(sf.Tag.Get(tagKey)) if fieldTag.Ignore { continue } ft := sf.Type if ft.Name() == "" && ft.Kind() == reflect.Ptr { ft = ft.Elem() } structField := buildField(f.Index, i, sf, fieldTag) structField.Type = ft if !sf.Anonymous || ft.Kind() != reflect.Struct { fields = append(fields, structField) if count[f.Type] > 1 { // If there were multiple instances, add a second, // so that the annihilation code will see a duplicate. // It only cares about the distinction between 1 or 2, // so don't bother generating any more copies. fields = append(fields, structField) } continue } // Record new anon struct to explore next round nextCount[ft]++ if nextCount[ft] == 1 { next = append(next, structField) } } } } return fields } // visibleFields will return a slice of fields which are visible based on // Go's standard visiblity rules with the exception of ties being broken // by depth and struct tag naming. // // Based on the enoding/json field filtering of the Go Stdlib // https://golang.org/src/encoding/json/encode.go typeField func. func visibleFields(fields []Field) []Field { // Delete all fields that are hidden by the Go rules for embedded fields, // except that fields with JSON tags are promoted. // The fields are sorted in primary order of name, secondary order // of field index length. Loop over names; for each name, delete // hidden fields by choosing the one dominant field that survives. out := fields[:0] for advance, i := 0, 0; i < len(fields); i += advance { // One iteration per name. // Find the sequence of fields with the name of this first field. fi := fields[i] name := fi.Name for advance = 1; i+advance < len(fields); advance++ { fj := fields[i+advance] if fj.Name != name { break } } if advance == 1 { // Only one field with this name out = append(out, fi) continue } dominant, ok := dominantField(fields[i : i+advance]) if ok { out = append(out, dominant) } } fields = out sort.Sort(fieldsByIndex(fields)) return fields } // dominantField looks through the fields, all of which are known to // have the same name, to find the single field that dominates the // others using Go's embedding rules, modified by the presence of // JSON tags. If there are multiple top-level fields, the boolean // will be false: This condition is an error in Go and we skip all // the fields. // // Based on the enoding/json field filtering of the Go Stdlib // https://golang.org/src/encoding/json/encode.go dominantField func. func dominantField(fields []Field) (Field, bool) { // The fields are sorted in increasing index-length order. The winner // must therefore be one with the shortest index length. Drop all // longer entries, which is easy: just truncate the slice. length := len(fields[0].Index) tagged := -1 // Index of first tagged field. for i, f := range fields { if len(f.Index) > length { fields = fields[:i] break } if f.NameFromTag { if tagged >= 0 { // Multiple tagged fields at the same level: conflict. // Return no field. return Field{}, false } tagged = i } } if tagged >= 0 { return fields[tagged], true } // All remaining fields have the same length. If there's more than one, // we have a conflict (two fields named "X" at the same level) and we // return no field. if len(fields) > 1 { return Field{}, false } return fields[0], true } // fieldsByName sorts field by name, breaking ties with depth, // then breaking ties with "name came from json tag", then // breaking ties with index sequence. // // Based on the enoding/json field filtering of the Go Stdlib // https://golang.org/src/encoding/json/encode.go fieldsByName type. type fieldsByName []Field func (x fieldsByName) Len() int { return len(x) } func (x fieldsByName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } func (x fieldsByName) Less(i, j int) bool { if x[i].Name != x[j].Name { return x[i].Name < x[j].Name } if len(x[i].Index) != len(x[j].Index) { return len(x[i].Index) < len(x[j].Index) } if x[i].NameFromTag != x[j].NameFromTag { return x[i].NameFromTag } return fieldsByIndex(x).Less(i, j) } // fieldsByIndex sorts field by index sequence. // // Based on the enoding/json field filtering of the Go Stdlib // https://golang.org/src/encoding/json/encode.go fieldsByIndex type. type fieldsByIndex []Field func (x fieldsByIndex) Len() int { return len(x) } func (x fieldsByIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } func (x fieldsByIndex) Less(i, j int) bool { for k, xik := range x[i].Index { if k >= len(x[j].Index) { return false } if xik != x[j].Index[k] { return xik < x[j].Index[k] } } return len(x[i].Index) < len(x[j].Index) } // DecoderFieldByIndex finds the field with the provided nested index, allocating // embedded parent structs if needed func DecoderFieldByIndex(v reflect.Value, index []int) reflect.Value { for i, x := range index { if i > 0 && v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct { if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) } v = v.Elem() } v = v.Field(x) } return v } // EncoderFieldByIndex finds the field with the provided nested index func EncoderFieldByIndex(v reflect.Value, index []int) (reflect.Value, bool) { for i, x := range index { if i > 0 && v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct { if v.IsNil() { return reflect.Value{}, false } v = v.Elem() } v = v.Field(x) } return v, true } smithy-go-1.20.3/document/internal/serde/field_cache.go000066400000000000000000000017211463735525100230220ustar00rootroot00000000000000package serde import ( "strings" "sync" ) var fieldCache fieldCacher type fieldCacher struct { cache sync.Map } func (c *fieldCacher) Load(t interface{}) (*CachedFields, bool) { if v, ok := c.cache.Load(t); ok { return v.(*CachedFields), true } return nil, false } func (c *fieldCacher) LoadOrStore(t interface{}, fs *CachedFields) (*CachedFields, bool) { v, ok := c.cache.LoadOrStore(t, fs) return v.(*CachedFields), ok } // CachedFields is a cache entry for a type's fields. type CachedFields struct { fields []Field fieldsByName map[string]int } // All returns all the fields for the cached type. func (f *CachedFields) All() []Field { return f.fields } // FieldByName retrieves a field by name. func (f *CachedFields) FieldByName(name string) (Field, bool) { if i, ok := f.fieldsByName[name]; ok { return f.fields[i], ok } for _, f := range f.fields { if strings.EqualFold(f.Name, name) { return f, true } } return Field{}, false } smithy-go-1.20.3/document/internal/serde/field_test.go000066400000000000000000000046131463735525100227410ustar00rootroot00000000000000package serde import ( "reflect" "testing" ) type testUnionValues struct { Name string Value interface{} } type unionSimple struct { A int B string C []string } type unionComplex struct { unionSimple A int } type unionTagged struct { A int `document:"A"` } type unionTaggedComplex struct { unionSimple unionTagged B string } func TestGetStructFields(t *testing.T) { var cases = []struct { in interface{} expect []testUnionValues }{ { in: unionSimple{1, "2", []string{"abc"}}, expect: []testUnionValues{ {"A", 1}, {"B", "2"}, {"C", []string{"abc"}}, }, }, { in: unionComplex{ unionSimple: unionSimple{1, "2", []string{"abc"}}, A: 2, }, expect: []testUnionValues{ {"B", "2"}, {"C", []string{"abc"}}, {"A", 2}, }, }, { in: unionTaggedComplex{ unionSimple: unionSimple{1, "2", []string{"abc"}}, unionTagged: unionTagged{3}, B: "3", }, expect: []testUnionValues{ {"C", []string{"abc"}}, {"A", 3}, {"B", "3"}, }, }, } for i, c := range cases { v := reflect.ValueOf(c.in) fields := GetStructFields(v.Type()) for j, f := range fields.All() { expected := c.expect[j] if e, a := expected.Name, f.Name; e != a { t.Errorf("%d:%d expect %v, got %v", i, j, e, f) } actual := v.FieldByIndex(f.Index).Interface() if e, a := expected.Value, actual; !reflect.DeepEqual(e, a) { t.Errorf("%d:%d expect %v, got %v", i, j, e, f) } } } } func TestCachedFields(t *testing.T) { type myStruct struct { Dog int CAT string bird bool } fields := GetStructFields(reflect.TypeOf(myStruct{})) const expectedNumFields = 2 if numFields := len(fields.All()); numFields != expectedNumFields { t.Errorf("expected number of fields to be %d but got %d", expectedNumFields, numFields) } cases := []struct { Name string FieldName string Found bool }{ {"Dog", "Dog", true}, {"dog", "Dog", true}, {"DOG", "Dog", true}, {"Yorkie", "", false}, {"Cat", "CAT", true}, {"cat", "CAT", true}, {"CAT", "CAT", true}, {"tiger", "", false}, {"bird", "", false}, } for _, c := range cases { f, found := fields.FieldByName(c.Name) if found != c.Found { t.Errorf("expected found to be %v but got %v", c.Found, found) } if found && f.Name != c.FieldName { t.Errorf("expected field name to be %s but got %s", c.FieldName, f.Name) } } } smithy-go-1.20.3/document/internal/serde/reflect.go000066400000000000000000000014171463735525100222420ustar00rootroot00000000000000package serde import ( "github.com/aws/smithy-go/document" "math/big" "reflect" "time" ) // ReflectTypeOf is a structure containing various reflect.Type members that are useful // to document Marshaler or Unmarshaler implementations. var ReflectTypeOf = struct { BigFloat reflect.Type BigInt reflect.Type DocumentNumber reflect.Type MapStringToInterface reflect.Type Time reflect.Type }{ BigFloat: reflect.TypeOf((*big.Float)(nil)).Elem(), BigInt: reflect.TypeOf((*big.Int)(nil)).Elem(), DocumentNumber: reflect.TypeOf((*document.Number)(nil)).Elem(), MapStringToInterface: reflect.TypeOf((map[string]interface{})(nil)), Time: reflect.TypeOf((*time.Time)(nil)).Elem(), } smithy-go-1.20.3/document/internal/serde/serde.go000066400000000000000000000045031463735525100217170ustar00rootroot00000000000000package serde import ( "reflect" ) // Indirect will walk a value's interface or pointer value types. Returning // the final value or the value a unmarshaler is defined on. // // Based on the enoding/json type reflect value type indirection in Go Stdlib // https://golang.org/src/encoding/json/decode.go Indirect func. func Indirect(v reflect.Value, decodingNull bool) reflect.Value { v0 := v haveAddr := false if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { v = v.Addr() haveAddr = true } for { if v.Kind() == reflect.Interface && !v.IsNil() { e := v.Elem() if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { haveAddr = false v = e continue } } if v.Kind() != reflect.Ptr { break } if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() { break } if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) } if haveAddr { v = v0 haveAddr = false } else { v = v.Elem() } } return v } // PtrToValue given the input value will dereference pointers and returning the element pointed to. func PtrToValue(in interface{}) interface{} { v := reflect.ValueOf(in) if v.Kind() == reflect.Ptr { v = v.Elem() } if !v.IsValid() { return nil } if v.Kind() == reflect.Ptr { return PtrToValue(v.Interface()) } return v.Interface() } // IsZeroValue returns whether v is the zero-value for its type. func IsZeroValue(v reflect.Value) bool { switch v.Kind() { case reflect.Invalid: return true case reflect.Array: return v.Len() == 0 case reflect.Map, reflect.Slice: return v.IsNil() case reflect.String: return v.Len() == 0 case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Interface, reflect.Ptr: return v.IsNil() } return false } // ValueElem walks interface and pointer types and returns the underlying element. func ValueElem(v reflect.Value) reflect.Value { switch v.Kind() { case reflect.Interface, reflect.Ptr: for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr { v = v.Elem() } } return v } smithy-go-1.20.3/document/internal/serde/tags.go000066400000000000000000000011561463735525100215540ustar00rootroot00000000000000package serde import ( "strings" ) // Tag represents the `document` struct field tag and associated options type Tag struct { Name string Ignore bool OmitEmpty bool } // ParseTag splits a struct field tag into its name and // comma-separated options. func ParseTag(tagStr string) (tag Tag) { parts := strings.Split(tagStr, ",") if len(parts) == 0 { return tag } if name := parts[0]; name == "-" { tag.Name = "" tag.Ignore = true } else { tag.Name = name tag.Ignore = false } for _, opt := range parts[1:] { switch opt { case "omitempty": tag.OmitEmpty = true } } return tag } smithy-go-1.20.3/document/json/000077500000000000000000000000001463735525100163175ustar00rootroot00000000000000smithy-go-1.20.3/document/json/decoder.go000066400000000000000000000216071463735525100202610ustar00rootroot00000000000000package json import ( "encoding/json" "fmt" "math/big" "reflect" "github.com/aws/smithy-go/document" "github.com/aws/smithy-go/document/internal/serde" ) // DecoderOptions is the set of options that can be configured for a Decoder. type DecoderOptions struct{} // Decoder is a Smithy document decoder for JSON based protocols. type Decoder struct { options DecoderOptions } // DecodeJSONInterface decodes the supported JSON input types and stores the result in the value pointed by toValue. // // If toValue is not a compatible type, or an error occurs while decoding DecodeJSONInterface will return an error. // // The supported input JSON types are: // bool -> JSON boolean // float64 -> JSON number // json.Number -> JSON number // string -> JSON string // []interface{} -> JSON array // map[string]interface{} -> JSON object // nil -> JSON null // func (d *Decoder) DecodeJSONInterface(input interface{}, toValue interface{}) error { if document.IsNoSerde(toValue) { return fmt.Errorf("unsupported type: %T", toValue) } v := reflect.ValueOf(toValue) if v.Kind() != reflect.Ptr || v.IsNil() || !v.IsValid() { return &document.InvalidUnmarshalError{Type: reflect.TypeOf(toValue)} } return d.decode(input, v, serde.Tag{}) } func (d *Decoder) decode(jv interface{}, rv reflect.Value, tag serde.Tag) error { if jv == nil { rv := serde.Indirect(rv, true) return d.decodeJSONNull(rv) } rv = serde.Indirect(rv, false) if err := d.unsupportedType(jv, rv); err != nil { return err } switch tv := jv.(type) { case bool: return d.decodeJSONBoolean(tv, rv) case json.Number: return d.decodeJSONNumber(tv, rv) case float64: return d.decodeJSONFloat64(tv, rv) case string: return d.decodeJSONString(tv, rv) case []interface{}: return d.decodeJSONArray(tv, rv) case map[string]interface{}: return d.decodeJSONObject(tv, rv) default: return fmt.Errorf("unsupported json type, %T", tv) } } func (d *Decoder) decodeJSONNull(rv reflect.Value) error { if rv.IsValid() && rv.CanSet() { rv.Set(reflect.Zero(rv.Type())) } return nil } func (d *Decoder) decodeJSONBoolean(tv bool, rv reflect.Value) error { switch rv.Kind() { case reflect.Bool, reflect.Interface: rv.Set(reflect.ValueOf(tv).Convert(rv.Type())) default: return &document.UnmarshalTypeError{Value: "bool", Type: rv.Type()} } return nil } func (d *Decoder) decodeJSONNumber(tv json.Number, rv reflect.Value) error { switch rv.Kind() { case reflect.Interface: rv.Set(reflect.ValueOf(document.Number(tv))) case reflect.String: // covers document.Number rv.SetString(tv.String()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i, err := tv.Int64() if err != nil { return err } if rv.OverflowInt(i) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("number overflow, %s", tv.String()), Type: rv.Type(), } } rv.SetInt(i) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: u, err := document.Number(tv).Uint64() if err != nil { return err } if rv.OverflowUint(u) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("number overflow, %s", tv.String()), Type: rv.Type(), } } rv.SetUint(u) case reflect.Float32: f, err := document.Number(tv).Float32() if err != nil { return err } if rv.OverflowFloat(f) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("float overflow, %s", tv.String()), Type: rv.Type(), } } rv.SetFloat(f) case reflect.Float64: f, err := document.Number(tv).Float64() if err != nil { return err } if rv.OverflowFloat(f) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("float overflow, %s", tv.String()), Type: rv.Type(), } } rv.SetFloat(f) default: rvt := rv.Type() switch { case rvt.ConvertibleTo(serde.ReflectTypeOf.BigFloat): sv := tv.String() f, ok := (&big.Float{}).SetString(sv) if !ok { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("invalid number format, %s", sv), Type: rv.Type(), } } rv.Set(reflect.ValueOf(*f).Convert(rvt)) case rvt.ConvertibleTo(serde.ReflectTypeOf.BigInt): sv := tv.String() i, ok := (&big.Int{}).SetString(sv, 10) if !ok { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("invalid number format, %s", sv), Type: rv.Type(), } } rv.Set(reflect.ValueOf(*i).Convert(rvt)) default: return &document.UnmarshalTypeError{Value: "number", Type: rv.Type()} } } return nil } func (d *Decoder) decodeJSONFloat64(tv float64, rv reflect.Value) error { switch rv.Kind() { case reflect.Interface: rv.Set(reflect.ValueOf(tv)) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i, accuracy := big.NewFloat(tv).Int64() if accuracy != big.Exact || rv.OverflowInt(i) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("number overflow, %e", tv), Type: rv.Type(), } } rv.SetInt(i) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: u, accuracy := big.NewFloat(tv).Uint64() if accuracy != big.Exact || rv.OverflowUint(u) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("number overflow, %e", tv), Type: rv.Type(), } } rv.SetUint(u) case reflect.Float32, reflect.Float64: if rv.OverflowFloat(tv) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("float overflow, %e", tv), Type: rv.Type(), } } rv.SetFloat(tv) default: rvt := rv.Type() switch { case rvt.ConvertibleTo(serde.ReflectTypeOf.BigFloat): f := big.NewFloat(tv) rv.Set(reflect.ValueOf(*f).Convert(rvt)) case rvt.ConvertibleTo(serde.ReflectTypeOf.BigInt): i, accuracy := big.NewFloat(tv).Int(nil) if accuracy != big.Exact { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("int overflow, %e", tv), Type: rv.Type(), } } rv.Set(reflect.ValueOf(*i).Convert(rvt)) default: return &document.UnmarshalTypeError{Value: "number", Type: rv.Type()} } } return nil } func (d *Decoder) decodeJSONArray(tv []interface{}, rv reflect.Value) error { var isArray bool switch rv.Kind() { case reflect.Slice: // Make room for the slice elements if needed if rv.IsNil() || rv.Cap() < len(tv) { rv.Set(reflect.MakeSlice(rv.Type(), 0, len(tv))) } case reflect.Array: // Limited to capacity of existing array. isArray = true case reflect.Interface: s := make([]interface{}, len(tv)) for i, av := range tv { if err := d.decode(av, reflect.ValueOf(&s[i]).Elem(), serde.Tag{}); err != nil { return err } } rv.Set(reflect.ValueOf(s)) return nil default: return &document.UnmarshalTypeError{Value: "list", Type: rv.Type()} } // If rv is not a slice, array for i := 0; i < rv.Cap() && i < len(tv); i++ { if !isArray { rv.SetLen(i + 1) } if err := d.decode(tv[i], rv.Index(i), serde.Tag{}); err != nil { return err } } return nil } func (d *Decoder) decodeJSONString(tv string, rv reflect.Value) error { switch rv.Kind() { case reflect.String: rv.SetString(tv) case reflect.Interface: // Ensure type aliasing is handled properly rv.Set(reflect.ValueOf(tv).Convert(rv.Type())) default: return &document.UnmarshalTypeError{Value: "string", Type: rv.Type()} } return nil } func (d *Decoder) decodeJSONObject(tv map[string]interface{}, rv reflect.Value) error { switch rv.Kind() { case reflect.Map: t := rv.Type() if t.Key().Kind() != reflect.String { return &document.UnmarshalTypeError{Value: "map string key", Type: t.Key()} } if rv.IsNil() { rv.Set(reflect.MakeMap(t)) } case reflect.Struct: if rv.CanInterface() && document.IsNoSerde(rv.Interface()) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("unsupported type"), Type: rv.Type(), } } case reflect.Interface: rv.Set(reflect.MakeMap(serde.ReflectTypeOf.MapStringToInterface)) rv = rv.Elem() default: return &document.UnmarshalTypeError{Value: "map", Type: rv.Type()} } if rv.Kind() == reflect.Map { for k, kv := range tv { key := reflect.New(rv.Type().Key()).Elem() key.SetString(k) elem := reflect.New(rv.Type().Elem()).Elem() if err := d.decode(kv, elem, serde.Tag{}); err != nil { return err } rv.SetMapIndex(key, elem) } } else if rv.Kind() == reflect.Struct { fields := serde.GetStructFields(rv.Type()) for k, kv := range tv { if f, ok := fields.FieldByName(k); ok { fv := serde.DecoderFieldByIndex(rv, f.Index) if err := d.decode(kv, fv, f.Tag); err != nil { return err } } } } return nil } func (d *Decoder) unsupportedType(jv interface{}, rv reflect.Value) error { if rv.Kind() == reflect.Interface && rv.NumMethod() != 0 { return &document.UnmarshalTypeError{Value: "non-empty interface", Type: rv.Type()} } if rv.Type().ConvertibleTo(serde.ReflectTypeOf.Time) { return &document.UnmarshalTypeError{ Type: rv.Type(), Value: fmt.Sprintf("time value: %v", jv), } } return nil } smithy-go-1.20.3/document/json/decoder_test.go000066400000000000000000000072171463735525100213210ustar00rootroot00000000000000package json_test import ( "math/big" "reflect" "testing" "time" "github.com/aws/smithy-go/document" "github.com/aws/smithy-go/document/internal/serde" "github.com/aws/smithy-go/document/json" ) var decodeArrayTestCases = map[string]testCase{ "array not enough capacity": { json: []byte(`["foo", "bar", "baz"]`), actual: func() interface{} { var v [2]string return &v }(), want: [2]string{"foo", "bar"}, }, } func TestDecoder_DecodeJSONInterface(t *testing.T) { t.Run("Shared", func(t *testing.T) { t.Run("Object", func(t *testing.T) { for name, tt := range sharedObjectTests { t.Run(name, func(t *testing.T) { testDecodeJSONInterface(t, tt) }) } }) t.Run("Array", func(t *testing.T) { for name, tt := range sharedArrayTestCases { t.Run(name, func(t *testing.T) { testDecodeJSONInterface(t, tt) }) } }) t.Run("Number", func(t *testing.T) { for name, tt := range sharedNumberTestCases { t.Run(name, func(t *testing.T) { testDecodeJSONInterface(t, tt) }) } }) t.Run("String", func(t *testing.T) { for name, tt := range sharedStringTests { t.Run(name, func(t *testing.T) { testDecodeJSONInterface(t, tt) }) } }) }) for name, tt := range decodeArrayTestCases { t.Run("Array", func(t *testing.T) { t.Run(name, func(t *testing.T) { testDecodeJSONInterface(t, tt) }) }) } } func TestNoSerde(t *testing.T) { type noSerde = document.NoSerde type InvalidType struct { FieldName string noSerde } var v InvalidType err := json.NewDecoder().DecodeJSONInterface(MustJSONUnmarshal([]byte(`{ "FieldName": "value" }`), true), &v) if err == nil { t.Fatalf("expect error, got nil") } type EmbedInvalidType struct { FieldName string TypeField InvalidType } var ev EmbedInvalidType err = json.NewDecoder().DecodeJSONInterface(MustJSONUnmarshal([]byte(`{ "FieldName": "value", "TypeField": { "FieldName": "value" } }`), true), &ev) if err == nil { t.Fatalf("expect error, got nil") } } func TestNewDecoderUnsupportedTypes(t *testing.T) { cases := []struct { input []byte value interface{} disableJSONNumber bool }{ { input: []byte(`1000000000`), value: func() interface{} { var t time.Time return &t }(), }, { input: []byte(`1000000000`), value: func() interface{} { var t time.Time return &t }(), disableJSONNumber: true, }, { input: []byte(`{}`), value: func() interface{} { var t time.Time return &t }(), }, { input: []byte(`{}`), value: func() interface{} { type def interface { String() } var i def return &i }(), }, } decoder := json.NewDecoder() for _, tt := range cases { err := decoder.DecodeJSONInterface(MustJSONUnmarshal(tt.input, !tt.disableJSONNumber), tt.value) if err == nil { t.Errorf("expect error, got nil") } } } func testDecodeJSONInterface(t *testing.T, tt testCase) { t.Helper() d := json.NewDecoder(func(options *json.DecoderOptions) { *options = tt.decoderOptions }) if err := d.DecodeJSONInterface(MustJSONUnmarshal(tt.json, !tt.disableJSONNumber), tt.actual); (err != nil) != tt.wantErr { t.Errorf("DecodeJSONInterface() error = %v, wantErr %v", err, tt.wantErr) } expect := serde.PtrToValue(tt.want) actual := serde.PtrToValue(tt.actual) if !reflect.DeepEqual(expect, actual) { t.Errorf("%v != %v", expect, actual) } } func cmpBigFloat() func(x big.Float, y big.Float) bool { return func(x, y big.Float) bool { return x.String() == y.String() } } func cmpBigInt() func(x big.Int, y big.Int) bool { return func(x, y big.Int) bool { return x.String() == y.String() } } smithy-go-1.20.3/document/json/doc.go000066400000000000000000000010411463735525100174070ustar00rootroot00000000000000// Package json provides a document Encoder and Decoder implementation that is used to implement Smithy document types // for JSON based protocols. The Encoder and Decoder implement the document.Marshaler and document.Unmarshaler // interfaces respectively. // // This package handles protocol specific implementation details about documents, and can not be used to construct // a document type for a service client. To construct a document type see each service clients respective document // package and NewLazyDocument function. package json smithy-go-1.20.3/document/json/encoder.go000066400000000000000000000175121463735525100202730ustar00rootroot00000000000000package json import ( "fmt" "math/big" "reflect" "github.com/aws/smithy-go/document" "github.com/aws/smithy-go/document/internal/serde" smithyjson "github.com/aws/smithy-go/encoding/json" ) // EncoderOptions is the set of options that can be configured for an Encoder. type EncoderOptions struct{} // Encoder is a Smithy document decoder for JSON based protocols. type Encoder struct { options EncoderOptions } // Encode returns the JSON encoding of v. func (e *Encoder) Encode(v interface{}) ([]byte, error) { encoder := smithyjson.NewEncoder() if err := e.encode(jsonValueProvider(encoder.Value), reflect.ValueOf(v), serde.Tag{}); err != nil { return nil, err } encodedBytes := encoder.Bytes() if len(encodedBytes) == 0 { return nil, nil } return encodedBytes, nil } // valueProvider is an interface for retrieving a JSON Value type used for encoding. type valueProvider interface { GetValue() smithyjson.Value } // jsonValueProvider is a valueProvider that returns the JSON value encoder as is. type jsonValueProvider smithyjson.Value func (p jsonValueProvider) GetValue() smithyjson.Value { return smithyjson.Value(p) } // jsonObjectKeyProvider is a valueProvider that returns a JSON value type for encoding a value for the given JSON object // key. type jsonObjectKeyProvider struct { Object *smithyjson.Object Key string } func (p jsonObjectKeyProvider) GetValue() smithyjson.Value { return p.Object.Key(p.Key) } // jsonArrayProvider is a valueProvider that returns a JSON value type for encoding a value in the given JSON array. type jsonArrayProvider struct { Array *smithyjson.Array } func (p jsonArrayProvider) GetValue() smithyjson.Value { return p.Array.Value() } func (e *Encoder) encode(vp valueProvider, rv reflect.Value, tag serde.Tag) error { // Zero values are serialized as null, or skipped if omitEmpty. if serde.IsZeroValue(rv) { if tag.OmitEmpty { return nil } return e.encodeZeroValue(vp, rv) } // Handle both pointers and interface conversion into types rv = serde.ValueElem(rv) switch rv.Kind() { case reflect.Invalid: if tag.OmitEmpty { return nil } vp.GetValue().Null() return nil case reflect.Struct: return e.encodeStruct(vp, rv) case reflect.Map: return e.encodeMap(vp, rv) case reflect.Slice, reflect.Array: return e.encodeSlice(vp, rv) case reflect.Chan, reflect.Func, reflect.UnsafePointer: // skip unsupported types return nil default: return e.encodeScalar(vp, rv) } } func (e *Encoder) encodeZeroValue(vp valueProvider, rv reflect.Value) error { switch rv.Kind() { case reflect.Invalid: vp.GetValue().Null() case reflect.Array: vp.GetValue().Array().Close() case reflect.Map, reflect.Slice: vp.GetValue().Null() case reflect.String: vp.GetValue().String("") case reflect.Bool: vp.GetValue().Boolean(false) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: vp.GetValue().Long(0) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: vp.GetValue().ULong(0) case reflect.Float32, reflect.Float64: vp.GetValue().Double(0) case reflect.Interface, reflect.Ptr: vp.GetValue().Null() default: return &document.InvalidMarshalError{Message: fmt.Sprintf("unknown value type: %s", rv.String())} } return nil } func (e *Encoder) encodeStruct(vp valueProvider, rv reflect.Value) error { if rv.CanInterface() && document.IsNoSerde(rv.Interface()) { return &document.UnmarshalTypeError{ Value: fmt.Sprintf("unsupported type"), Type: rv.Type(), } } switch { case rv.Type().ConvertibleTo(serde.ReflectTypeOf.Time): return &document.InvalidMarshalError{ Message: fmt.Sprintf("unsupported type %s", rv.Type().String()), } case rv.Type().ConvertibleTo(serde.ReflectTypeOf.BigFloat): fallthrough case rv.Type().ConvertibleTo(serde.ReflectTypeOf.BigInt): return e.encodeNumber(vp, rv) } object := vp.GetValue().Object() defer object.Close() fields := serde.GetStructFields(rv.Type()) for _, f := range fields.All() { if f.Name == "" { return &document.InvalidMarshalError{Message: "map key cannot be empty"} } fv, found := serde.EncoderFieldByIndex(rv, f.Index) if !found { continue } err := e.encode(jsonObjectKeyProvider{ Object: object, Key: f.Name, }, fv, f.Tag) if err != nil { return err } } return nil } func (e *Encoder) encodeMap(vp valueProvider, rv reflect.Value) error { object := vp.GetValue().Object() defer object.Close() for _, key := range rv.MapKeys() { keyName := fmt.Sprint(key.Interface()) if keyName == "" { return &document.InvalidMarshalError{Message: "map key cannot be empty"} } ev := rv.MapIndex(key) err := e.encode(jsonObjectKeyProvider{ Object: object, Key: keyName, }, ev, serde.Tag{}) if err != nil { return err } } return nil } func (e *Encoder) encodeSlice(value valueProvider, rv reflect.Value) error { array := value.GetValue().Array() defer array.Close() for i := 0; i < rv.Len(); i++ { err := e.encode(jsonArrayProvider{Array: array}, rv.Index(i), serde.Tag{}) if err != nil { return err } } return nil } func (e *Encoder) encodeScalar(vp valueProvider, rv reflect.Value) error { if rv.Type() == serde.ReflectTypeOf.DocumentNumber { number := rv.Interface().(document.Number) if !isValidJSONNumber(number.String()) { return &document.InvalidMarshalError{Message: fmt.Sprintf("invalid number literal: %s", number)} } vp.GetValue().Write([]byte(number)) } switch rv.Kind() { case reflect.Bool: vp.GetValue().Boolean(rv.Bool()) case reflect.String: vp.GetValue().String(rv.String()) default: // Fallback to encoding numbers, will return invalid type if not supported err := e.encodeNumber(vp, rv) if err != nil { return err } } return nil } func (e *Encoder) encodeNumber(vp valueProvider, rv reflect.Value) error { switch rv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: vp.GetValue().Long(rv.Int()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: vp.GetValue().ULong(rv.Uint()) case reflect.Float32: vp.GetValue().Float(float32(rv.Float())) case reflect.Float64: vp.GetValue().Double(rv.Float()) default: rvt := rv.Type() switch { case rvt.ConvertibleTo(serde.ReflectTypeOf.BigInt): bi := rv.Convert(serde.ReflectTypeOf.BigInt).Interface().(big.Int) vp.GetValue().BigInteger(&bi) case rvt.ConvertibleTo(serde.ReflectTypeOf.BigFloat): bf := rv.Convert(serde.ReflectTypeOf.BigFloat).Interface().(big.Float) vp.GetValue().BigDecimal(&bf) default: return &document.InvalidMarshalError{Message: fmt.Sprintf("incompatible type: %s", rvt.String())} } } return nil } // isValidJSONNumber reports whether s is a valid JSON number literal. // From https://golang.org/src/encoding/json/encode.go#L652 isValidNumber // Copyright 2010 The Go Authors. func isValidJSONNumber(s string) bool { // This function implements the JSON numbers grammar. // See https://tools.ietf.org/html/rfc7159#section-6 // and https://www.json.org/img/number.png if s == "" { return false } // Optional - if s[0] == '-' { s = s[1:] if s == "" { return false } } // Digits switch { default: return false case s[0] == '0': s = s[1:] case '1' <= s[0] && s[0] <= '9': s = s[1:] for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { s = s[1:] } } // . followed by 1 or more digits. if len(s) >= 2 && s[0] == '.' && '0' <= s[1] && s[1] <= '9' { s = s[2:] for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { s = s[1:] } } // e or E followed by an optional - or + and // 1 or more digits. if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') { s = s[1:] if s[0] == '+' || s[0] == '-' { s = s[1:] if s == "" { return false } } for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { s = s[1:] } } // Make sure we are at the end. return s == "" }smithy-go-1.20.3/document/json/encoder_test.go000066400000000000000000000034751463735525100213350ustar00rootroot00000000000000package json_test import ( "reflect" "testing" "time" "github.com/aws/smithy-go/document" "github.com/aws/smithy-go/document/json" ) func TestEncoder_Encode(t *testing.T) { t.Run("Object", func(t *testing.T) { for name, tt := range sharedObjectTests { t.Run(name, func(t *testing.T) { testEncode(t, tt) }) } }) t.Run("Array", func(t *testing.T) { for name, tt := range sharedArrayTestCases { t.Run(name, func(t *testing.T) { testEncode(t, tt) }) } }) t.Run("Number", func(t *testing.T) { for name, tt := range sharedNumberTestCases { t.Run(name, func(t *testing.T) { testEncode(t, tt) }) } }) t.Run("String", func(t *testing.T) { for name, tt := range sharedStringTests { t.Run(name, func(t *testing.T) { testEncode(t, tt) }) } }) } func TestNewEncoderUnsupportedTypes(t *testing.T) { type customTime time.Time type noSerde = document.NoSerde type NestedThing struct { SomeThing string noSerde } type Thing struct { OtherThing string NestedThing NestedThing } cases := []interface{}{ time.Now().UTC(), customTime(time.Now().UTC()), Thing{OtherThing: "foo", NestedThing: NestedThing{SomeThing: "bar"}}, } encoder := json.NewEncoder() for _, tt := range cases { _, err := encoder.Encode(tt) if err == nil { t.Errorf("expect error, got nil") } } } func testEncode(t *testing.T, tt testCase) { t.Helper() e := json.NewEncoder(func(options *json.EncoderOptions) { *options = tt.encoderOptions }) encodeBytes, err := e.Encode(tt.actual) if (err != nil) != tt.wantErr { t.Errorf("Encode() error = %v, wantErr %v", err, tt.wantErr) } expect := MustJSONUnmarshal(tt.json, !tt.disableJSONNumber) got := MustJSONUnmarshal(encodeBytes, !tt.disableJSONNumber) if !reflect.DeepEqual(expect, got) { t.Errorf("%v != %v", expect, got) } } smithy-go-1.20.3/document/json/json.go000066400000000000000000000010261463735525100176160ustar00rootroot00000000000000package json // NewEncoder returns an Encoder for serializing Smithy documents for JSON based protocols. func NewEncoder(optFns ...func(options *EncoderOptions)) *Encoder { o := EncoderOptions{} for _, fn := range optFns { fn(&o) } return &Encoder{ options: o, } } // NewDecoder returns a Decoder for deserializing Smithy documents for JSON based protocols. func NewDecoder(optFns ...func(*DecoderOptions)) *Decoder { o := DecoderOptions{} for _, fn := range optFns { fn(&o) } return &Decoder{ options: o, } } smithy-go-1.20.3/document/json/shared_test.go000066400000000000000000000235351463735525100211630ustar00rootroot00000000000000package json_test import ( "bytes" json2 "encoding/json" "math" "math/big" "strconv" "github.com/aws/smithy-go/document" "github.com/aws/smithy-go/document/json" "github.com/aws/smithy-go/ptr" ) type StructA struct { FieldName string FieldPtrName *string FieldRename string `document:"field_rename"` FieldIgnored string `document:"-"` FieldOmitEmpty string `document:",omitempty"` FieldPtrOmitEmpty *string `document:",omitempty"` FieldRenameOmitEmpty string `document:"field_rename_omit_empty,omitempty"` FieldNestedStruct *StructA `document:"field_nested_struct"` FieldNestedStructOmitEmpty *StructA `document:"field_nested_struct_omit_empty,omitempty"` fieldUnexported string StructB } type StructB struct { FieldName string `document:"field_name"` } type testCase struct { decoderOptions json.DecoderOptions encoderOptions json.EncoderOptions disableJSONNumber bool json []byte actual, want interface{} wantErr bool } var sharedStringTests = map[string]testCase{ "string": { json: []byte(`"foo"`), actual: func() interface{} { var v string return &v }(), want: "foo", }, "interface{}": { json: []byte(`"foo"`), actual: func() interface{} { var v interface{} return &v }(), want: "foo", }, } var sharedObjectTests = map[string]testCase{ "null for pointer type": { json: []byte(`null`), actual: func() interface{} { var v *StructA return &v }(), }, "zero value": { json: []byte(`{ "FieldName": "", "FieldPtrName": null, "field_name": "", "field_nested_struct": null, "field_rename": "" }`), actual: func() interface{} { var v StructA return &v }(), want: StructA{}, }, "filled json structure": { json: []byte(`{ "FieldName": "a", "FieldPtrName": "b", "field_rename": "c", "FieldOmitEmpty": "d", "FieldPtrOmitEmpty": "e", "field_rename_omit_empty": "f", "field_nested_struct": { "FieldName": "a", "FieldPtrName": null, "field_name": "", "field_nested_struct": null, "field_rename": "" }, "field_nested_struct_omit_empty": { "FieldName": "a", "FieldPtrName": null, "field_name": "", "field_nested_struct": null, "field_rename": "" }, "field_name": "A" }`), actual: func() interface{} { var v *StructA return &v }(), want: &StructA{ FieldName: "a", FieldPtrName: ptr.String("b"), FieldRename: "c", FieldOmitEmpty: "d", FieldPtrOmitEmpty: ptr.String("e"), FieldRenameOmitEmpty: "f", FieldNestedStruct: &StructA{ FieldName: "a", }, FieldNestedStructOmitEmpty: &StructA{ FieldName: "a", }, StructB: StructB{ FieldName: "A", }, }, }, } var sharedArrayTestCases = map[string]testCase{ "slice": { json: []byte(`["foo", "bar", "baz"]`), actual: func() interface{} { var v []string return &v }(), want: []string{"foo", "bar", "baz"}, }, "array": { json: []byte(`["foo", "bar", "baz"]`), actual: func() interface{} { var v [3]string return &v }(), want: [3]string{"foo", "bar", "baz"}, }, "interface{}": { json: []byte(`["foo", "bar", "baz"]`), actual: func() interface{} { var v interface{} return &v }(), want: []interface{}{"foo", "bar", "baz"}, }, } var sharedNumberTestCases = map[string]testCase{ "json.Number to interface{}": { json: []byte(`3.14159`), actual: func() interface{} { var v interface{} return &v }(), want: ptrNumber("3.14159"), }, "json float64 to interface{}": { json: []byte(`3.14159`), actual: func() interface{} { var v interface{} return &v }(), want: ptr.Float64(3.14159), disableJSONNumber: true, }, "json.Number to document.Number": { json: []byte(`3.14159`), actual: func() interface{} { var v document.Number return &v }(), want: document.Number("3.14159"), }, "json.Number to *document.Number": { json: []byte(`3.14159`), actual: func() interface{} { var v *document.Number return &v }(), want: document.Number("3.14159"), }, /* int, int16, int32, int64 */ "json.Number to int": { json: []byte(`2147483647`), actual: func() interface{} { var x int return &x }(), want: ptr.Int(2147483647), }, "json float64 to int": { json: []byte(`2147483647`), actual: func() interface{} { var x int return &x }(), want: ptr.Int(2147483647), disableJSONNumber: true, }, "json.Number to int8": { json: []byte(`127`), actual: func() interface{} { var x int8 return &x }(), want: ptr.Int8(127), }, "json float64 to int8": { json: []byte(`127`), actual: func() interface{} { var x int8 return &x }(), want: ptr.Int8(127), disableJSONNumber: true, }, "json.Number to int16": { json: []byte(`32767`), actual: func() interface{} { var x int16 return &x }(), want: ptr.Int16(32767), }, "json float64 to int16": { json: []byte(`32767`), actual: func() interface{} { var x int16 return &x }(), want: ptr.Int16(32767), disableJSONNumber: true, }, "json.Number to int32": { json: []byte(`2147483647`), actual: func() interface{} { var x int32 return &x }(), want: ptr.Int32(2147483647), }, "json float64 to int32": { json: []byte(`2147483647`), actual: func() interface{} { var x int32 return &x }(), want: ptr.Int32(2147483647), disableJSONNumber: true, }, "json.Number to int64": { json: []byte("9223372036854775807"), actual: func() interface{} { var x int64 return &x }(), want: ptr.Int64(9223372036854775807), }, "json float64 to int64": { json: []byte("2147483648"), actual: func() interface{} { var x int64 return &x }(), want: ptr.Int64(2147483648), disableJSONNumber: true, }, /* uint, uint16, uint32, uint64 */ "json.Number to uint": { json: []byte(`4294967295`), actual: func() interface{} { var x uint return &x }(), want: ptr.Uint(4294967295), }, "json float64 to uint": { json: []byte(`4294967295`), actual: func() interface{} { var x uint return &x }(), want: ptr.Uint(4294967295), disableJSONNumber: true, }, "json.Number to uint8": { json: []byte(`255`), actual: func() interface{} { var x uint8 return &x }(), want: ptr.Uint8(255), }, "json float64 to uint8": { json: []byte(`255`), actual: func() interface{} { var x uint8 return &x }(), want: ptr.Uint8(255), disableJSONNumber: true, }, "json.Number to uint16": { json: []byte(`65535`), actual: func() interface{} { var x uint16 return &x }(), want: ptr.Uint16(65535), }, "json float64 to uint16": { json: []byte(`65535`), actual: func() interface{} { var x uint16 return &x }(), want: ptr.Uint16(65535), disableJSONNumber: true, }, "json.Number to uint32": { json: []byte(`4294967295`), actual: func() interface{} { var x uint32 return &x }(), want: ptr.Uint32(4294967295), }, "json float64 to uint32": { json: []byte(`4294967295`), actual: func() interface{} { var x uint32 return &x }(), want: ptr.Uint32(4294967295), disableJSONNumber: true, }, "json.Number to uint64": { json: []byte("18446744073709551615"), actual: func() interface{} { var x uint64 return &x }(), want: ptr.Uint64(18446744073709551615), }, "json float64 to uint64": { json: []byte("4294967295"), actual: func() interface{} { var x uint64 return &x }(), want: ptr.Uint64(4294967295), disableJSONNumber: true, }, /* float32, float64 */ "json.Number to float32": { json: []byte(strconv.FormatFloat(math.MaxFloat32, 'e', -1, 32)), actual: func() interface{} { var x float32 return &x }(), want: ptr.Float32(math.MaxFloat32), }, "json float64 to float32": { json: []byte(strconv.FormatFloat(3.14159, 'e', -1, 32)), actual: func() interface{} { var x float32 return &x }(), want: ptr.Float32(3.14159), disableJSONNumber: true, }, "json.Number to float64": { json: []byte(strconv.FormatFloat(math.MaxFloat64, 'e', -1, 64)), actual: func() interface{} { var x float64 return &x }(), want: ptr.Float64(math.MaxFloat64), }, "json float64 to float64": { json: []byte(strconv.FormatFloat(3.14159, 'e', -1, 64)), actual: func() interface{} { var x float64 return &x }(), want: ptr.Float64(3.14159), disableJSONNumber: true, }, /* Arbitrary Number Sizes */ "json.Number to big.Float": { json: []byte(strconv.FormatFloat(math.MaxFloat64, 'e', -1, 64)), actual: func() interface{} { var x big.Float return &x }(), want: func() *big.Float { // this is slightly different than big.NewFloat(math.MaxFloat64) x, _ := (&big.Float{}).SetString(strconv.FormatFloat(math.MaxFloat64, 'e', -1, 64)) return x }(), }, "float64 to big.Float": { json: []byte(strconv.FormatFloat(math.MaxFloat64, 'e', -1, 64)), actual: func() interface{} { var x big.Float return &x }(), want: func() *big.Float { return big.NewFloat(math.MaxFloat64) }(), disableJSONNumber: true, }, "json.Number to big.Int": { json: []byte(strconv.FormatInt(math.MaxInt64, 10)), actual: func() interface{} { var x big.Int return &x }(), want: func() *big.Int { return big.NewInt(math.MaxInt64) }(), }, "float64 to big.Int": { json: []byte(strconv.FormatInt(math.MaxInt32, 10)), actual: func() interface{} { var x big.Int return &x }(), want: func() *big.Int { return big.NewInt(math.MaxInt32) }(), disableJSONNumber: true, }, } func ptrNumber(number document.Number) *document.Number { return &number } func MustJSONUnmarshal(v []byte, useJSONNumber bool) interface{} { var jv interface{} decoder := json2.NewDecoder(bytes.NewReader(v)) if useJSONNumber { decoder.UseNumber() } if err := decoder.Decode(&jv); err != nil { panic(err) } return jv } smithy-go-1.20.3/encoding/000077500000000000000000000000001463735525100153165ustar00rootroot00000000000000smithy-go-1.20.3/encoding/cbor/000077500000000000000000000000001463735525100162435ustar00rootroot00000000000000smithy-go-1.20.3/encoding/cbor/cbor.go000066400000000000000000000107551463735525100175270ustar00rootroot00000000000000// Package cbor implements partial encoding/decoding of concise binary object // representation (CBOR) described in [RFC 8949]. // // This package is intended for use only by the smithy client runtime. The // exported API therein is not considered stable and is subject to breaking // changes without notice. More specifically, this package implements a subset // of the RFC 8949 specification required to support the Smithy RPCv2-CBOR // protocol and is NOT suitable for general application use. // // The following principal restrictions apply: // - Map (major type 5) keys can only be strings. // - Float16 (major type 7, 25) values can be read but not encoded. Any // float16 encountered during decode is converted to float32. // - Indefinite-length values can be read but not encoded. Since the encoding // API operates strictly off of a constructed syntax tree, the length of each // data item in a Value will always be known and the encoder will always // generate definite-length variants. // // It is the responsibility of the caller to determine whether a decoded CBOR // integral or floating-point Value is suitable for its target (e.g. whether // the value of a CBOR Uint fits into a field modeled as a Smithy short). // // All CBOR tags (major type 6) are implicitly supported since the // encoder/decoder does not attempt to interpret a tag's contents. It is the // responsibility of the caller to both provide valid Tag values to encode and // to assert that a decoded Tag's contents are valid for its tag ID (e.g. // ensuring whether a Tag with ID 1, indicating an enclosed epoch timestamp, // actually contains a valid integral or floating-point CBOR Value). // // [RFC 8949]: https://www.rfc-editor.org/rfc/rfc8949.html package cbor // Value describes a CBOR data item. // // The following types implement Value: // - [Uint] // - [NegInt] // - [Slice] // - [String] // - [List] // - [Map] // - [Tag] // - [Bool] // - [Nil] // - [Undefined] // - [Float32] // - [Float64] type Value interface { len() int encode(p []byte) int } var ( _ Value = Uint(0) _ Value = NegInt(0) _ Value = Slice(nil) _ Value = String("") _ Value = List(nil) _ Value = Map(nil) _ Value = (*Tag)(nil) _ Value = Bool(false) _ Value = (*Nil)(nil) _ Value = (*Undefined)(nil) _ Value = Float32(0) _ Value = Float64(0) ) // Uint describes a CBOR uint (major type 0) in the range [0, 2^64-1]. type Uint uint64 // NegInt describes a CBOR negative int (major type 1) in the range [-2^64, -1]. // // The "true negative" value of a type 1 is specified by RFC 8949 to be -1 // minus the encoded value. The encoder/decoder applies this bias // automatically, e.g. the integral -100 is represented as NegInt(100), which // will which encode to/from hex 3863 (major 1, minor 24, argument 99). // // This implicitly means that the lower bound of this type -2^64 is represented // as the wraparound value NegInt(0). Deserializer implementations should take // care to guard against this case when deriving a value for a signed integral // type which was encoded as NegInt. type NegInt uint64 // Slice describes a CBOR byte slice (major type 2). type Slice []byte // String describes a CBOR text string (major type 3). type String string // List describes a CBOR list (major type 4). type List []Value // Map describes a CBOR map (major type 5). // // The type signature of the map's key is restricted to string as it is in // Smithy. type Map map[string]Value // Tag describes a CBOR-tagged value (major type 6). type Tag struct { ID uint64 Value Value } // Bool describes a boolean value (major type 7, argument 20/21). type Bool bool // Nil is the `nil` / `null` literal (major type 7, argument 22). type Nil struct{} // Undefined is the `undefined` literal (major type 7, argument 23). type Undefined struct{} // Float32 describes an IEEE 754 single-precision floating-point number // (major type 7, argument 26). // // Go does not natively support float16, all values encoded as such (major type // 7, argument 25) must be represented by this variant instead. type Float32 float32 // Float64 describes an IEEE 754 double-precision floating-point number // (major type 7, argument 27). type Float64 float64 // Encode returns a byte slice that encodes the given Value. func Encode(v Value) []byte { p := make([]byte, v.len()) v.encode(p) return p } // Decode returns the Value encoded in the given byte slice. func Decode(p []byte) (Value, error) { v, _, err := decode(p) if err != nil { return nil, err } return v, nil } smithy-go-1.20.3/encoding/cbor/coerce.go000066400000000000000000000133111463735525100200310ustar00rootroot00000000000000package cbor import ( "fmt" "math/big" "time" ) func fmtNegint(v NegInt) string { if v == 0 { return "-2^64" } return fmt.Sprintf("-%d", v) } // AsInt8 coerces a Value to its int8 representation if possible. func AsInt8(v Value) (int8, error) { const max8 = 0x7f switch vv := v.(type) { case Uint: if vv > max8 { return 0, fmt.Errorf("cbor uint %d exceeds max int8 value", vv) } return int8(vv), nil case NegInt: if vv > max8+1 || vv == 0 { return 0, fmt.Errorf("cbor negint %s exceeds min int8 value", fmtNegint(vv)) } return -int8(vv), nil } return 0, fmt.Errorf("unexpected value type %T", v) } // AsInt16 coerces a Value to its int16 representation if possible. func AsInt16(v Value) (int16, error) { const max16 = 0x7fff switch vv := v.(type) { case Uint: if vv > max16 { return 0, fmt.Errorf("cbor uint %d exceeds max int16 value", vv) } return int16(vv), nil case NegInt: if vv > max16+1 || vv == 0 { return 0, fmt.Errorf("cbor negint %s exceeds min int16 value", fmtNegint(vv)) } return -int16(vv), nil } return 0, fmt.Errorf("unexpected value type %T", v) } // AsInt32 coerces a Value to its int32 representation if possible. func AsInt32(v Value) (int32, error) { const max32 = 0x7fffffff switch vv := v.(type) { case Uint: if vv > max32 { return 0, fmt.Errorf("cbor uint %d exceeds max int32 value", vv) } return int32(vv), nil case NegInt: if vv > max32+1 || vv == 0 { return 0, fmt.Errorf("cbor negint %s exceeds min int32 value", fmtNegint(vv)) } return -int32(vv), nil } return 0, fmt.Errorf("unexpected value type %T", v) } // AsInt64 coerces a Value to its int64 representation if possible. func AsInt64(v Value) (int64, error) { const max64 = 0x7fffffff_ffffffff switch vv := v.(type) { case Uint: if vv > max64 { return 0, fmt.Errorf("cbor uint %d exceeds max int64 value", vv) } return int64(vv), nil case NegInt: if vv > max64+1 || vv == 0 { return 0, fmt.Errorf("cbor negint %s exceeds min int64 value", fmtNegint(vv)) } return -int64(vv), nil } return 0, fmt.Errorf("unexpected value type %T", v) } // AsFloat32 coerces a Value to its float32 representation if possible. // // A float32 may be represented by any of the following alternatives: // - cbor uint (if within lossless range) // - cbor -int (if within lossless range) func AsFloat32(v Value) (float32, error) { const maxLosslessFloat32 = 1 << 24 switch vv := v.(type) { case Float32: return float32(vv), nil case Uint: if vv > maxLosslessFloat32 { return 0, fmt.Errorf("cbor uint %d exceeds max lossless float32 value", vv) } return float32(vv), nil case NegInt: if vv > maxLosslessFloat32 || vv == 0 { return 0, fmt.Errorf("cbor negint %s exceeds min lossless float32 value", fmtNegint(vv)) } return -float32(vv), nil } return 0, fmt.Errorf("unexpected value type %T", v) } // AsFloat64 coerces a Value to its float64 representation if possible. // // A float64 may be represented by any of the following alternatives: // - float32 // - cbor uint (if within lossless range) // - cbor -int (if within lossless range) func AsFloat64(v Value) (float64, error) { const maxLosslessFloat64 = 1 << 54 switch vv := v.(type) { case Float64: return float64(vv), nil case Float32: return float64(vv), nil case Uint: if vv > maxLosslessFloat64 { return 0, fmt.Errorf("cbor uint %d exceeds max lossless float64 value", vv) } return float64(vv), nil case NegInt: if vv > maxLosslessFloat64 || vv == 0 { return 0, fmt.Errorf("cbor negint %s exceeds min lossless float64 value", fmtNegint(vv)) } return -float64(vv), nil } return 0, fmt.Errorf("unexpected value type %T", v) } // AsTime coerces a Value to its time.Time representation if possible. // // This coercion will check that the given Value is a Tag with the registered // number (1) for epoch time. The value for time.Time within that tag may be // derived from any of the following: // - float32 // - float64 // - cbor uint (within int64 bounds) // - cbor -int (within int64 bounds) // // Tag number 0 (date-time RFC3339) is not supported. func AsTime(v Value) (time.Time, error) { const tagEpoch = 1 tag, ok := v.(*Tag) if !ok { return time.Time{}, fmt.Errorf("unexpected value type %T", v) } if tag.ID != tagEpoch { return time.Time{}, fmt.Errorf("unexpected tag ID %d", tag.ID) } switch vv := tag.Value.(type) { case Float32: return time.UnixMilli(int64(vv * 1e3)), nil case Float64: return time.UnixMilli(int64(vv * 1e3)), nil } as64, err := AsInt64(tag.Value) // will handle fail on non-int types if err != nil { return time.Time{}, fmt.Errorf("coerce tag value: %w", err) } return time.Unix(as64, 0), nil } // AsBigInt coerces a Value to its big.Int representation if possible. // // A BigInt may be represented by any of the following: // - Uint // - NegInt // - Tag (type 2/3, where tagged value is a Slice) // - Nil func AsBigInt(v Value) (*big.Int, error) { switch vv := v.(type) { case Uint: return new(big.Int).SetUint64(uint64(vv)), nil case NegInt: i := new(big.Int) if vv == 0 { i.SetBytes([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0}) } else { i.SetUint64(uint64(vv)) } return i.Neg(i), nil case *Tag: return asBigIntFromTag(vv) case *Nil: return nil, nil default: return nil, fmt.Errorf("unexpected value type %T", v) } } func asBigIntFromTag(tv *Tag) (*big.Int, error) { const tagpos = 2 const tagneg = 3 if tv.ID != tagpos && tv.ID != tagneg { return nil, fmt.Errorf("unexpected tag ID %d", tv.ID) } bytes, ok := tv.Value.(Slice) if !ok { return nil, fmt.Errorf("unexpected tag value type %T", tv.Value) } i := new(big.Int).SetBytes([]byte(bytes)) if tv.ID == tagneg { i.Sub(big.NewInt(-1), i) } return i, nil } smithy-go-1.20.3/encoding/cbor/coerce_test.go000066400000000000000000000251401463735525100210730ustar00rootroot00000000000000package cbor import ( "fmt" "math/big" "strings" "testing" "time" ) func TestAsInt8(t *testing.T) { const maxv = 0x7f for name, c := range map[string]struct { In Value Expect int8 Err string }{ "wrong type": { In: String(""), Err: "unexpected value type cbor.String", }, "uint oob": { In: Uint(maxv + 1), Err: fmt.Sprintf("cbor uint %d exceeds", maxv+1), }, "negint oob": { In: NegInt(maxv + 2), Err: fmt.Sprintf("cbor negint %s exceeds", fmtNegint(NegInt(maxv+2))), }, "negint wrap oob": { In: NegInt(0), Err: "cbor negint -2^64 exceeds", }, "uint ok min": { In: Uint(0), Expect: 0, }, "uint ok max": { In: Uint(maxv), Expect: maxv, }, "negint ok min": { In: NegInt(1), Expect: -1, }, "negint ok max": { In: NegInt(maxv + 1), Expect: -maxv - 1, }, } { t.Run(name, func(t *testing.T) { actual, err := AsInt8(c.In) if c.Err == "" { if err != nil { t.Fatalf("expect no err, got %v", err) } if actual != c.Expect { t.Fatalf("%v != %v", c.Expect, actual) } } else { if err == nil { t.Fatalf("expect err %v", err) } if !strings.Contains(err.Error(), c.Err) { t.Fatalf("'%v' does not contain '%s'", err, c.Err) } } }) } } func TestAsInt16(t *testing.T) { const maxv = 0x7fff for name, c := range map[string]struct { In Value Expect int16 Err string }{ "wrong type": { In: String(""), Err: "unexpected value type cbor.String", }, "uint oob": { In: Uint(maxv + 1), Err: fmt.Sprintf("cbor uint %d exceeds", maxv+1), }, "negint oob": { In: NegInt(maxv + 2), Err: fmt.Sprintf("cbor negint %s exceeds", fmtNegint(NegInt(maxv+2))), }, "negint wrap oob": { In: NegInt(0), Err: "cbor negint -2^64 exceeds", }, "uint ok min": { In: Uint(0), Expect: 0, }, "uint ok max": { In: Uint(maxv), Expect: maxv, }, "negint ok min": { In: NegInt(1), Expect: -1, }, "negint ok max": { In: NegInt(maxv + 1), Expect: -maxv - 1, }, } { t.Run(name, func(t *testing.T) { actual, err := AsInt16(c.In) if c.Err == "" { if err != nil { t.Fatalf("expect no err, got %v", err) } if actual != c.Expect { t.Fatalf("%v != %v", c.Expect, actual) } } else { if err == nil { t.Fatalf("expect err %v", err) } if !strings.Contains(err.Error(), c.Err) { t.Fatalf("'%v' does not contain '%s'", err, c.Err) } } }) } } func TestAsInt32(t *testing.T) { const maxv = 0x7fffffff for name, c := range map[string]struct { In Value Expect int32 Err string }{ "wrong type": { In: String(""), Err: "unexpected value type cbor.String", }, "uint oob": { In: Uint(maxv + 1), Err: fmt.Sprintf("cbor uint %d exceeds", Uint(maxv+1)), }, "negint oob": { In: NegInt(maxv + 2), Err: fmt.Sprintf("cbor negint %s exceeds", fmtNegint(NegInt(maxv+2))), }, "negint wrap oob": { In: NegInt(0), Err: "cbor negint -2^64 exceeds", }, "uint ok min": { In: Uint(0), Expect: 0, }, "uint ok max": { In: Uint(maxv), Expect: maxv, }, "negint ok min": { In: NegInt(1), Expect: -1, }, "negint ok max": { In: NegInt(maxv + 1), Expect: -maxv - 1, }, } { t.Run(name, func(t *testing.T) { actual, err := AsInt32(c.In) if c.Err == "" { if err != nil { t.Fatalf("expect no err, got %v", err) } if actual != c.Expect { t.Fatalf("%v != %v", c.Expect, actual) } } else { if err == nil { t.Fatalf("expect err %v", err) } if !strings.Contains(err.Error(), c.Err) { t.Fatalf("'%v' does not contain '%s'", err, c.Err) } } }) } } func TestAsInt64(t *testing.T) { const maxv = 0x7fffffff_ffffffff for name, c := range map[string]struct { In Value Expect int64 Err string }{ "wrong type": { In: String(""), Err: "unexpected value type cbor.String", }, "uint oob": { In: Uint(uint64(maxv) + 1), Err: fmt.Sprintf("cbor uint %d exceeds", uint64(maxv)+1), }, "negint oob": { In: NegInt(uint64(maxv) + 2), Err: fmt.Sprintf("cbor negint %s exceeds", fmtNegint(NegInt(uint64(maxv)+2))), }, "negint wrap oob": { In: NegInt(0), Err: "cbor negint -2^64 exceeds", }, "uint ok min": { In: Uint(0), Expect: 0, }, "uint ok max": { In: Uint(maxv), Expect: maxv, }, "negint ok min": { In: NegInt(1), Expect: -1, }, "negint ok max": { In: NegInt(maxv + 1), Expect: -maxv - 1, }, } { t.Run(name, func(t *testing.T) { actual, err := AsInt64(c.In) if c.Err == "" { if err != nil { t.Fatalf("expect no err, got %v", err) } if actual != c.Expect { t.Fatalf("%v != %v", c.Expect, actual) } } else { if err == nil { t.Fatalf("expect err %v", err) } if !strings.Contains(err.Error(), c.Err) { t.Fatalf("'%v' does not contain '%s'", err, c.Err) } } }) } } func TestAsFloat32(t *testing.T) { const maxv = 1 << 24 for name, c := range map[string]struct { In Value Expect float32 Err string }{ "wrong type": { In: String(""), Err: "unexpected value type cbor.String", }, "uint oob": { In: Uint(maxv + 1), Err: fmt.Sprintf("cbor uint %d exceeds", maxv+1), }, "negint oob": { In: NegInt(maxv + 2), Err: fmt.Sprintf("cbor negint %s exceeds", fmtNegint(NegInt(maxv+2))), }, "negint wrap oob": { In: NegInt(0), Err: "cbor negint -2^64 exceeds", }, "uint ok min": { In: Uint(0), Expect: 0, }, "uint ok max": { In: Uint(maxv), Expect: maxv, }, "negint ok min": { In: NegInt(1), Expect: -1, }, "negint ok max": { In: NegInt(maxv), Expect: -maxv, }, "direct": { In: Float32(0.5), Expect: 0.5, }, } { t.Run(name, func(t *testing.T) { actual, err := AsFloat32(c.In) if c.Err == "" { if err != nil { t.Fatalf("expect no err, got %v", err) } if actual != c.Expect { t.Fatalf("%v != %v", c.Expect, actual) } } else { if err == nil { t.Fatalf("expect err %v", err) } if !strings.Contains(err.Error(), c.Err) { t.Fatalf("'%v' does not contain '%s'", err, c.Err) } } }) } } func TestAsFloat64(t *testing.T) { const maxv = 1 << 54 for name, c := range map[string]struct { In Value Expect float64 Err string }{ "wrong type": { In: String(""), Err: "unexpected value type cbor.String", }, "uint oob": { In: Uint(maxv + 1), Err: fmt.Sprintf("cbor uint %d exceeds", Uint(maxv+1)), }, "negint oob": { In: NegInt(maxv + 2), Err: fmt.Sprintf("cbor negint %s exceeds", fmtNegint(NegInt(maxv+2))), }, "negint wrap oob": { In: NegInt(0), Err: "cbor negint -2^64 exceeds", }, "uint ok min": { In: Uint(0), Expect: 0, }, "uint ok max": { In: Uint(maxv), Expect: maxv, }, "negint ok min": { In: NegInt(1), Expect: -1, }, "negint ok max": { In: NegInt(maxv), Expect: -maxv, }, "float32": { In: Float32(0.5), Expect: 0.5, }, "direct": { In: Float64(0.5), Expect: 0.5, }, } { t.Run(name, func(t *testing.T) { actual, err := AsFloat64(c.In) if c.Err == "" { if err != nil { t.Fatalf("expect no err, got %v", err) } if actual != c.Expect { t.Fatalf("%v != %v", c.Expect, actual) } } else { if err == nil { t.Fatalf("expect err %v", err) } if !strings.Contains(err.Error(), c.Err) { t.Fatalf("'%v' does not contain '%s'", err, c.Err) } } }) } } func TestAsTime(t *testing.T) { for name, c := range map[string]struct { In Value Expect time.Time Err string }{ "wrong type": { In: String(""), Err: "unexpected value type cbor.String", }, "wrong tag": { In: &Tag{ID: 2}, Err: "unexpected tag ID 2", }, "wrong tag value": { In: &Tag{ID: 1, Value: String("")}, Err: "coerce tag value: unexpected value type cbor.String", }, "no tag value": { In: &Tag{ID: 1}, Err: "coerce tag value: unexpected value type ", }, "negint": { In: &Tag{ID: 1, Value: Uint(4)}, Expect: time.UnixMilli(4000), }, "float32": { In: &Tag{ID: 1, Value: Float32(3.997)}, Expect: time.UnixMilli(3997), }, "float64": { In: &Tag{ID: 1, Value: Float64(3.997)}, Expect: time.UnixMilli(3997), }, } { t.Run(name, func(t *testing.T) { actual, err := AsTime(c.In) if c.Err == "" { if err != nil { t.Fatalf("expect no err, got %v", err) } if actual != c.Expect { t.Fatalf("%v != %v", c.Expect, actual) } } else { if err == nil { t.Fatalf("expect err %v", err) } if !strings.Contains(err.Error(), c.Err) { t.Fatalf("'%v' does not contain '%s'", err, c.Err) } } }) } } func TestAsBigInt(t *testing.T) { for name, c := range map[string]struct { In Value Expect *big.Int Err string }{ "wrong type": { In: String(""), Err: "unexpected value type cbor.String", }, "wrong tag": { In: &Tag{ID: 1}, Err: "unexpected tag ID 1", }, "wrong tag value": { In: &Tag{ID: 2, Value: String("")}, Err: "unexpected tag value type cbor.String", }, "uint min": { In: Uint(0), Expect: big.NewInt(0), }, "uint max": { In: Uint(0xffffffff_ffffffff), Expect: new(big.Int).SetBytes( []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ), }, "negint min": { In: NegInt(1), Expect: big.NewInt(-1), }, "negint max": { In: NegInt(0), Expect: func() *big.Int { i := new(big.Int).SetBytes( []byte{1, 0, 0, 0, 0, 0, 0, 0, 0}, ) return i.Neg(i) }(), }, "tag 2": { In: &Tag{ ID: 2, Value: Slice{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, }, Expect: new(big.Int).SetBytes( []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, ), }, "tag 3": { In: &Tag{ ID: 3, Value: Slice{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, }, Expect: func() *big.Int { i := new(big.Int).SetBytes( []byte{1, 0, 0, 0, 0, 0, 0, 0, 0}, ) return i.Neg(i) }(), }, "nil": { In: &Nil{}, Expect: nil, }, } { t.Run(name, func(t *testing.T) { actual, err := AsBigInt(c.In) if c.Err == "" { if err != nil { t.Fatalf("expect no err, got %v", err) } if c.Expect.Cmp(actual) != 0 { t.Fatalf("%v != %v", c.Expect, actual) } } else { if err == nil { t.Fatalf("expect err %v", err) } if !strings.Contains(err.Error(), c.Err) { t.Fatalf("'%v' does not contain '%s'", err, c.Err) } } }) } } smithy-go-1.20.3/encoding/cbor/const.go000066400000000000000000000013451463735525100177230ustar00rootroot00000000000000package cbor // major type in LSB position type majorType byte const ( majorTypeUint majorType = iota majorTypeNegInt majorTypeSlice majorTypeString majorTypeList majorTypeMap majorTypeTag majorType7 ) // masks for major/minor component in encoded head const ( maskMajor = 0b111 << 5 maskMinor = 0b11111 ) // minor value encodings to represent arg bit length (and indefinite) const ( minorArg1 = 24 minorArg2 = 25 minorArg4 = 26 minorArg8 = 27 minorIndefinite = 31 ) // minor sentinels for everything in major 7 const ( major7False = 20 major7True = 21 major7Nil = 22 major7Undefined = 23 major7Float16 = minorArg2 major7Float32 = minorArg4 major7Float64 = minorArg8 ) smithy-go-1.20.3/encoding/cbor/decode.go000066400000000000000000000160541463735525100200230ustar00rootroot00000000000000package cbor import ( "encoding/binary" "fmt" "math" ) func decode(p []byte) (Value, int, error) { if len(p) == 0 { return nil, 0, fmt.Errorf("unexpected end of payload") } switch peekMajor(p) { case majorTypeUint: return decodeUint(p) case majorTypeNegInt: return decodeNegInt(p) case majorTypeSlice: return decodeSlice(p, majorTypeSlice) case majorTypeString: s, n, err := decodeSlice(p, majorTypeString) return String(s), n, err case majorTypeList: return decodeList(p) case majorTypeMap: return decodeMap(p) case majorTypeTag: return decodeTag(p) default: // majorType7 return decodeMajor7(p) } } func decodeUint(p []byte) (Uint, int, error) { i, off, err := decodeArgument(p) if err != nil { return 0, 0, fmt.Errorf("decode argument: %w", err) } return Uint(i), off, nil } func decodeNegInt(p []byte) (NegInt, int, error) { i, off, err := decodeArgument(p) if err != nil { return 0, 0, fmt.Errorf("decode argument: %w", err) } return NegInt(i + 1), off, nil } // this routine is used for both string and slice major types, the value of // inner specifies which context we're in (needed for validating subsegments // inside indefinite encodings) func decodeSlice(p []byte, inner majorType) (Slice, int, error) { minor := peekMinor(p) if minor == minorIndefinite { return decodeSliceIndefinite(p, inner) } slen, off, err := decodeArgument(p) if err != nil { return nil, 0, fmt.Errorf("decode argument: %w", err) } p = p[off:] if uint64(len(p)) < slen { return nil, 0, fmt.Errorf("slice len %d greater than remaining buf len", slen) } return Slice(p[:slen]), off + int(slen), nil } func decodeSliceIndefinite(p []byte, inner majorType) (Slice, int, error) { p = p[1:] s := Slice{} for off := 0; len(p) > 0; { if p[0] == 0xff { return s, off + 2, nil } if major := peekMajor(p); major != inner { return nil, 0, fmt.Errorf("unexpected major type %d in indefinite slice", major) } if peekMinor(p) == minorIndefinite { return nil, 0, fmt.Errorf("nested indefinite slice") } ss, n, err := decodeSlice(p, inner) if err != nil { return nil, 0, fmt.Errorf("decode subslice: %w", err) } p = p[n:] s = append(s, ss...) off += n } return nil, 0, fmt.Errorf("expected break marker") } func decodeList(p []byte) (List, int, error) { minor := peekMinor(p) if minor == minorIndefinite { return decodeListIndefinite(p) } alen, off, err := decodeArgument(p) if err != nil { return nil, 0, fmt.Errorf("decode argument: %w", err) } p = p[off:] l := List{} for i := 0; i < int(alen); i++ { item, n, err := decode(p) if err != nil { return nil, 0, fmt.Errorf("decode item: %w", err) } p = p[n:] l = append(l, item) off += n } return l, off, nil } func decodeListIndefinite(p []byte) (List, int, error) { p = p[1:] l := List{} for off := 0; len(p) > 0; { if p[0] == 0xff { return l, off + 2, nil } item, n, err := decode(p) if err != nil { return nil, 0, fmt.Errorf("decode item: %w", err) } p = p[n:] l = append(l, item) off += n } return nil, 0, fmt.Errorf("expected break marker") } func decodeMap(p []byte) (Map, int, error) { minor := peekMinor(p) if minor == minorIndefinite { return decodeMapIndefinite(p) } maplen, off, err := decodeArgument(p) if err != nil { return nil, 0, fmt.Errorf("decode argument: %w", err) } p = p[off:] mp := Map{} for i := 0; i < int(maplen); i++ { if len(p) == 0 { return nil, 0, fmt.Errorf("unexpected end of payload") } if major := peekMajor(p); major != majorTypeString { return nil, 0, fmt.Errorf("unexpected major type %d for map key", major) } key, kn, err := decodeSlice(p, majorTypeString) if err != nil { return nil, 0, fmt.Errorf("decode key: %w", err) } p = p[kn:] value, vn, err := decode(p) if err != nil { return nil, 0, fmt.Errorf("decode value: %w", err) } p = p[vn:] mp[string(key)] = value off += kn + vn } return mp, off, nil } func decodeMapIndefinite(p []byte) (Map, int, error) { p = p[1:] mp := Map{} for off := 0; len(p) > 0; { if p[0] == 0xff { return mp, off + 2, nil } if major := peekMajor(p); major != majorTypeString { return nil, 0, fmt.Errorf("unexpected major type %d for map key", major) } key, kn, err := decodeSlice(p, majorTypeString) if err != nil { return nil, 0, fmt.Errorf("decode key: %w", err) } p = p[kn:] value, vn, err := decode(p) if err != nil { return nil, 0, fmt.Errorf("decode value: %w", err) } p = p[vn:] mp[string(key)] = value off += kn + vn } return nil, 0, fmt.Errorf("expected break marker") } func decodeTag(p []byte) (*Tag, int, error) { id, off, err := decodeArgument(p) if err != nil { return nil, 0, fmt.Errorf("decode argument: %w", err) } p = p[off:] v, n, err := decode(p) if err != nil { return nil, 0, fmt.Errorf("decode value: %w", err) } return &Tag{ID: id, Value: v}, off + n, nil } func decodeMajor7(p []byte) (Value, int, error) { switch m := peekMinor(p); m { case major7True, major7False: return Bool(m == major7True), 1, nil case major7Nil: return &Nil{}, 1, nil case major7Undefined: return &Undefined{}, 1, nil case major7Float16: if len(p) < 3 { return nil, 0, fmt.Errorf("incomplete float16 at end of buf") } b := binary.BigEndian.Uint16(p[1:]) return Float32(math.Float32frombits(float16to32(b))), 3, nil case major7Float32: if len(p) < 5 { return nil, 0, fmt.Errorf("incomplete float32 at end of buf") } b := binary.BigEndian.Uint32(p[1:]) return Float32(math.Float32frombits(b)), 5, nil case major7Float64: if len(p) < 9 { return nil, 0, fmt.Errorf("incomplete float64 at end of buf") } b := binary.BigEndian.Uint64(p[1:]) return Float64(math.Float64frombits(b)), 9, nil default: return nil, 0, fmt.Errorf("unexpected minor value %d", m) } } func peekMajor(p []byte) majorType { return majorType(p[0] & maskMajor >> 5) } func peekMinor(p []byte) byte { return p[0] & maskMinor } // pulls the next argument out of the buffer // // expects one of the sized arguments and will error otherwise - callers that // need to check for the indefinite flag must do so externally func decodeArgument(p []byte) (uint64, int, error) { minor := peekMinor(p) if minor < minorArg1 { return uint64(minor), 1, nil } switch minor { case minorArg1, minorArg2, minorArg4, minorArg8: argLen := mtol(minor) if len(p) < argLen+1 { return 0, 0, fmt.Errorf("arg len %d greater than remaining buf len", argLen) } return readArgument(p[1:], argLen), argLen + 1, nil default: return 0, 0, fmt.Errorf("unexpected minor value %d", minor) } } // minor value to arg len in bytes, assumes minor was checked to be in [24,27] func mtol(minor byte) int { if minor == minorArg1 { return 1 } else if minor == minorArg2 { return 2 } else if minor == minorArg4 { return 4 } return 8 } func readArgument(p []byte, len int) uint64 { if len == 1 { return uint64(p[0]) } else if len == 2 { return uint64(binary.BigEndian.Uint16(p)) } else if len == 4 { return uint64(binary.BigEndian.Uint32(p)) } return uint64(binary.BigEndian.Uint64(p)) } smithy-go-1.20.3/encoding/cbor/decode_test.go000066400000000000000000001016371463735525100210640ustar00rootroot00000000000000package cbor import ( "math" "reflect" "strings" "testing" ) func TestDecode_InvalidArgument(t *testing.T) { for name, c := range map[string]struct { In []byte Err string }{ "uint/1": { []byte{0<<5 | 24}, "arg len 1 greater than remaining buf len", }, "uint/2": { []byte{0<<5 | 25, 0}, "arg len 2 greater than remaining buf len", }, "uint/4": { []byte{0<<5 | 26, 0, 0, 0}, "arg len 4 greater than remaining buf len", }, "uint/8": { []byte{0<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, "arg len 8 greater than remaining buf len", }, "uint/?": { []byte{0<<5 | 31}, "unexpected minor value 31", }, "negint/1": { []byte{1<<5 | 24}, "arg len 1 greater than remaining buf len", }, "negint/2": { []byte{1<<5 | 25, 0}, "arg len 2 greater than remaining buf len", }, "negint/4": { []byte{1<<5 | 26, 0, 0, 0}, "arg len 4 greater than remaining buf len", }, "negint/8": { []byte{1<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, "arg len 8 greater than remaining buf len", }, "negint/?": { []byte{1<<5 | 31}, "unexpected minor value 31", }, "slice/1": { []byte{2<<5 | 24}, "arg len 1 greater than remaining buf len", }, "slice/2": { []byte{2<<5 | 25, 0}, "arg len 2 greater than remaining buf len", }, "slice/4": { []byte{2<<5 | 26, 0, 0, 0}, "arg len 4 greater than remaining buf len", }, "slice/8": { []byte{2<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, "arg len 8 greater than remaining buf len", }, "string/1": { []byte{3<<5 | 24}, "arg len 1 greater than remaining buf len", }, "string/2": { []byte{3<<5 | 25, 0}, "arg len 2 greater than remaining buf len", }, "string/4": { []byte{3<<5 | 26, 0, 0, 0}, "arg len 4 greater than remaining buf len", }, "string/8": { []byte{3<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, "arg len 8 greater than remaining buf len", }, "list/1": { []byte{4<<5 | 24}, "arg len 1 greater than remaining buf len", }, "list/2": { []byte{4<<5 | 25, 0}, "arg len 2 greater than remaining buf len", }, "list/4": { []byte{4<<5 | 26, 0, 0, 0}, "arg len 4 greater than remaining buf len", }, "list/8": { []byte{4<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, "arg len 8 greater than remaining buf len", }, "map/1": { []byte{5<<5 | 24}, "arg len 1 greater than remaining buf len", }, "map/2": { []byte{5<<5 | 25, 0}, "arg len 2 greater than remaining buf len", }, "map/4": { []byte{5<<5 | 26, 0, 0, 0}, "arg len 4 greater than remaining buf len", }, "map/8": { []byte{5<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, "arg len 8 greater than remaining buf len", }, "tag/1": { []byte{6<<5 | 24}, "arg len 1 greater than remaining buf len", }, "tag/2": { []byte{6<<5 | 25, 0}, "arg len 2 greater than remaining buf len", }, "tag/4": { []byte{6<<5 | 26, 0, 0, 0}, "arg len 4 greater than remaining buf len", }, "tag/8": { []byte{6<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, "arg len 8 greater than remaining buf len", }, "tag/?": { []byte{6<<5 | 31}, "unexpected minor value 31", }, "major7/float16": { []byte{7<<5 | 25, 0}, "incomplete float16 at end of buf", }, "major7/float32": { []byte{7<<5 | 26, 0, 0, 0}, "incomplete float32 at end of buf", }, "major7/float64": { []byte{7<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, "incomplete float64 at end of buf", }, "major7/?": { []byte{7<<5 | 31}, "unexpected minor value 31", }, } { t.Run(name, func(t *testing.T) { _, _, err := decode(c.In) if err == nil { t.Errorf("expect err %s", c.Err) } if aerr := err.Error(); !strings.Contains(aerr, c.Err) { t.Errorf("expect err %s, got %s", c.Err, aerr) } }) } } func TestDecode_InvalidSlice(t *testing.T) { for name, c := range map[string]struct { In []byte Err string }{ "slice/1, not enough bytes": { []byte{2<<5 | 24, 1}, "slice len 1 greater than remaining buf len", }, "slice/?, no break": { []byte{2<<5 | 31}, "expected break marker", }, "slice/?, invalid nested major": { []byte{2<<5 | 31, 3<<5 | 0}, "unexpected major type 3 in indefinite slice", }, "slice/?, nested indefinite": { []byte{2<<5 | 31, 2<<5 | 31}, "nested indefinite slice", }, "slice/?, invalid nested definite": { []byte{2<<5 | 31, 2<<5 | 24, 1}, "decode subslice: slice len 1 greater than remaining buf len", }, "string/1, not enough bytes": { []byte{3<<5 | 24, 1}, "slice len 1 greater than remaining buf len", }, "string/?, no break": { []byte{3<<5 | 31}, "expected break marker", }, "string/?, invalid nested major": { []byte{3<<5 | 31, 2<<5 | 0}, "unexpected major type 2 in indefinite slice", }, "string/?, nested indefinite": { []byte{3<<5 | 31, 3<<5 | 31}, "nested indefinite slice", }, "string/?, invalid nested definite": { []byte{3<<5 | 31, 3<<5 | 24, 1}, "decode subslice: slice len 1 greater than remaining buf len", }, } { t.Run(name, func(t *testing.T) { _, _, err := decode(c.In) if err == nil { t.Errorf("expect err %s", c.Err) } if aerr := err.Error(); !strings.Contains(aerr, c.Err) { t.Errorf("expect err %s, got %s", c.Err, aerr) } }) } } func TestDecode_InvalidList(t *testing.T) { for name, c := range map[string]struct { In []byte Err string }{ "[] / eof after head": { []byte{4<<5 | 1}, "unexpected end of payload", }, "[] / invalid item": { []byte{4<<5 | 1, 0<<5 | 24}, "arg len 1 greater than remaining buf len", }, "[_ ] / no break": { []byte{4<<5 | 31}, "expected break marker", }, "[_ ] / invalid item": { []byte{4<<5 | 31, 0<<5 | 24}, "arg len 1 greater than remaining buf len", }, } { t.Run(name, func(t *testing.T) { _, _, err := decode(c.In) if err == nil { t.Errorf("expect err %s", c.Err) } if aerr := err.Error(); !strings.Contains(aerr, c.Err) { t.Errorf("expect err %s, got %s", c.Err, aerr) } }) } } func TestDecode_InvalidMap(t *testing.T) { for name, c := range map[string]struct { In []byte Err string }{ "{} / eof after head": { []byte{5<<5 | 1}, "unexpected end of payload", }, "{} / non-string key": { []byte{5<<5 | 1, 0}, "unexpected major type 0 for map key", }, "{} / invalid key": { []byte{5<<5 | 1, 3<<5 | 24, 1}, "slice len 1 greater than remaining buf len", }, "{} / invalid value": { []byte{5<<5 | 1, 3<<5 | 3, 0x66, 0x6f, 0x6f, 0<<5 | 24}, "arg len 1 greater than remaining buf len", }, "{_ } / no break": { []byte{5<<5 | 31}, "expected break marker", }, "{_ } / non-string key": { []byte{5<<5 | 31, 0}, "unexpected major type 0 for map key", }, "{_ } / invalid key": { []byte{5<<5 | 31, 3<<5 | 24, 1}, "slice len 1 greater than remaining buf len", }, "{_ } / invalid value": { []byte{5<<5 | 31, 3<<5 | 3, 0x66, 0x6f, 0x6f, 0<<5 | 24}, "arg len 1 greater than remaining buf len", }, } { t.Run(name, func(t *testing.T) { _, _, err := decode(c.In) if err == nil { t.Errorf("expect err %s", c.Err) } if aerr := err.Error(); !strings.Contains(aerr, c.Err) { t.Errorf("expect err %s, got %s", c.Err, aerr) } }) } } func TestDecode_InvalidTag(t *testing.T) { for name, c := range map[string]struct { In []byte Err string }{ "invalid value": { []byte{6<<5 | 1, 0<<5 | 24}, "arg len 1 greater than remaining buf len", }, "eof": { []byte{6<<5 | 1}, "unexpected end of payload", }, } { t.Run(name, func(t *testing.T) { _, _, err := decode(c.In) if err == nil { t.Errorf("expect err %s", c.Err) } if aerr := err.Error(); !strings.Contains(aerr, c.Err) { t.Errorf("expect err %s, got %s", c.Err, aerr) } }) } } func TestDecode_Atomic(t *testing.T) { for name, c := range map[string]struct { In []byte Expect Value }{ "uint/0/min": { []byte{0<<5 | 0}, Uint(0), }, "uint/0/max": { []byte{0<<5 | 23}, Uint(23), }, "uint/1/min": { []byte{0<<5 | 24, 0}, Uint(0), }, "uint/1/max": { []byte{0<<5 | 24, 0xff}, Uint(0xff), }, "uint/2/min": { []byte{0<<5 | 25, 0, 0}, Uint(0), }, "uint/2/max": { []byte{0<<5 | 25, 0xff, 0xff}, Uint(0xffff), }, "uint/4/min": { []byte{0<<5 | 26, 0, 0, 0, 0}, Uint(0), }, "uint/4/max": { []byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}, Uint(0xffffffff), }, "uint/8/min": { []byte{0<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}, Uint(0), }, "uint/8/max": { []byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, Uint(0xffffffff_ffffffff), }, "negint/0/min": { []byte{1<<5 | 0}, NegInt(1), }, "negint/0/max": { []byte{1<<5 | 23}, NegInt(24), }, "negint/1/min": { []byte{1<<5 | 24, 0}, NegInt(1), }, "negint/1/max": { []byte{1<<5 | 24, 0xff}, NegInt(0x100), }, "negint/2/min": { []byte{1<<5 | 25, 0, 0}, NegInt(1), }, "negint/2/max": { []byte{1<<5 | 25, 0xff, 0xff}, NegInt(0x10000), }, "negint/4/min": { []byte{1<<5 | 26, 0, 0, 0, 0}, NegInt(1), }, "negint/4/max": { []byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}, NegInt(0x100000000), }, "negint/8/min": { []byte{1<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}, NegInt(1), }, "negint/8/max": { []byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}, NegInt(0xffffffff_ffffffff), }, "true": { []byte{7<<5 | major7True}, Bool(true), }, "false": { []byte{7<<5 | major7False}, Bool(false), }, "null": { []byte{7<<5 | major7Nil}, &Nil{}, }, "undefined": { []byte{7<<5 | major7Undefined}, &Undefined{}, }, "float16/+Inf": { []byte{7<<5 | major7Float16, 0x7c, 0}, Float32(math.Float32frombits(0x7f800000)), }, "float16/-Inf": { []byte{7<<5 | major7Float16, 0xfc, 0}, Float32(math.Float32frombits(0xff800000)), }, "float16/NaN/MSB": { []byte{7<<5 | major7Float16, 0x7e, 0}, Float32(math.Float32frombits(0x7fc00000)), }, "float16/NaN/LSB": { []byte{7<<5 | major7Float16, 0x7c, 1}, Float32(math.Float32frombits(0x7f802000)), }, "float32": { []byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}, Float32(math.Float32frombits(0x7f800000)), }, "float64": { []byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}, Float64(math.Float64frombits(0x7ff00000_00000000)), }, } { t.Run(name, func(t *testing.T) { actual, n, err := decode(c.In) if err != nil { t.Errorf("expect no err, got %v", err) } if n != len(c.In) { t.Errorf("didn't decode whole buffer") } assertValue(t, c.Expect, actual) }) } } func TestDecode_DefiniteSlice(t *testing.T) { for name, c := range map[string]struct { In []byte Expect Value }{ "len = 0": { []byte{2<<5 | 0}, Slice{}, }, "len > 0": { []byte{2<<5 | 3, 0x66, 0x6f, 0x6f}, Slice{0x66, 0x6f, 0x6f}, }, } { t.Run(name, func(t *testing.T) { actual, n, err := decode(c.In) if err != nil { t.Errorf("expect no err, got %v", err) } if n != len(c.In) { t.Errorf("didn't decode whole buffer") } assertValue(t, c.Expect, actual) }) } } func TestDecode_IndefiniteSlice(t *testing.T) { for name, c := range map[string]struct { In []byte Expect Value }{ "len = 0": { []byte{2<<5 | 31, 0xff}, Slice{}, }, "len = 0, explicit": { []byte{2<<5 | 31, 2<<5 | 0, 0xff}, Slice{}, }, "len = 0, len > 0": { []byte{ 2<<5 | 31, 2<<5 | 0, 2<<5 | 3, 0x66, 0x6f, 0x6f, 0xff, }, Slice{0x66, 0x6f, 0x6f}, }, "len > 0, len = 0": { []byte{ 2<<5 | 31, 2<<5 | 3, 0x66, 0x6f, 0x6f, 2<<5 | 0, 0xff, }, Slice{0x66, 0x6f, 0x6f}, }, "len > 0, len > 0": { []byte{ 2<<5 | 31, 2<<5 | 3, 0x66, 0x6f, 0x6f, 2<<5 | 3, 0x66, 0x6f, 0x6f, 0xff, }, Slice{0x66, 0x6f, 0x6f, 0x66, 0x6f, 0x6f}, }, } { t.Run(name, func(t *testing.T) { actual, n, err := decode(c.In) if err != nil { t.Errorf("expect no err, got %v", err) } if n != len(c.In) { t.Errorf("didn't decode whole buffer") } assertValue(t, c.Expect, actual) }) } } func TestDecode_DefiniteString(t *testing.T) { for name, c := range map[string]struct { In []byte Expect Value }{ "len = 0": { []byte{3<<5 | 0}, String(""), }, "len > 0": { []byte{3<<5 | 3, 0x66, 0x6f, 0x6f}, String("foo"), }, } { t.Run(name, func(t *testing.T) { actual, n, err := decode(c.In) if err != nil { t.Errorf("expect no err, got %v", err) } if n != len(c.In) { t.Errorf("didn't decode whole buffer") } assertValue(t, c.Expect, actual) }) } } func TestDecode_IndefiniteString(t *testing.T) { for name, c := range map[string]struct { In []byte Expect Value }{ "len = 0": { []byte{3<<5 | 31, 0xff}, String(""), }, "len = 0, explicit": { []byte{3<<5 | 31, 3<<5 | 0, 0xff}, String(""), }, "len = 0, len > 0": { []byte{ 3<<5 | 31, 3<<5 | 0, 3<<5 | 3, 0x66, 0x6f, 0x6f, 0xff, }, String("foo"), }, "len > 0, len = 0": { []byte{ 3<<5 | 31, 3<<5 | 3, 0x66, 0x6f, 0x6f, 3<<5 | 0, 0xff, }, String("foo"), }, "len > 0, len > 0": { []byte{ 3<<5 | 31, 3<<5 | 3, 0x66, 0x6f, 0x6f, 3<<5 | 3, 0x66, 0x6f, 0x6f, 0xff, }, String("foofoo"), }, } { t.Run(name, func(t *testing.T) { actual, n, err := decode(c.In) if err != nil { t.Errorf("expect no err, got %v", err) } if n != len(c.In) { t.Errorf("didn't decode whole buffer") } assertValue(t, c.Expect, actual) }) } } func TestDecode_List(t *testing.T) { for name, c := range map[string]struct { In []byte Expect Value }{ "[uint/0/min]": { In: withDefiniteList([]byte{0<<5 | 0}), Expect: List{Uint(0)}, }, "[uint/0/max]": { In: withDefiniteList([]byte{0<<5 | 23}), Expect: List{Uint(23)}, }, "[uint/1/min]": { In: withDefiniteList([]byte{0<<5 | 24, 0}), Expect: List{Uint(0)}, }, "[uint/1/max]": { In: withDefiniteList([]byte{0<<5 | 24, 0xff}), Expect: List{Uint(0xff)}, }, "[uint/2/min]": { In: withDefiniteList([]byte{0<<5 | 25, 0, 0}), Expect: List{Uint(0)}, }, "[uint/2/max]": { In: withDefiniteList([]byte{0<<5 | 25, 0xff, 0xff}), Expect: List{Uint(0xffff)}, }, "[uint/4/min]": { In: withDefiniteList([]byte{0<<5 | 26, 0, 0, 0, 0}), Expect: List{Uint(0)}, }, "[uint/4/max]": { In: withDefiniteList([]byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}), Expect: List{Uint(0xffffffff)}, }, "[uint/8/min]": { In: withDefiniteList([]byte{0<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), Expect: List{Uint(0)}, }, "[uint/8/max]": { In: withDefiniteList([]byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), Expect: List{Uint(0xffffffff_ffffffff)}, }, "[negint/0/min]": { In: withDefiniteList([]byte{1<<5 | 0}), Expect: List{NegInt(1)}, }, "[negint/0/max]": { In: withDefiniteList([]byte{1<<5 | 23}), Expect: List{NegInt(24)}, }, "[negint/1/min]": { In: withDefiniteList([]byte{1<<5 | 24, 0}), Expect: List{NegInt(1)}, }, "[negint/1/max]": { In: withDefiniteList([]byte{1<<5 | 24, 0xff}), Expect: List{NegInt(0x100)}, }, "[negint/2/min]": { In: withDefiniteList([]byte{1<<5 | 25, 0, 0}), Expect: List{NegInt(1)}, }, "[negint/2/max]": { In: withDefiniteList([]byte{1<<5 | 25, 0xff, 0xff}), Expect: List{NegInt(0x10000)}, }, "[negint/4/min]": { In: withDefiniteList([]byte{1<<5 | 26, 0, 0, 0, 0}), Expect: List{NegInt(1)}, }, "[negint/4/max]": { In: withDefiniteList([]byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}), Expect: List{NegInt(0x100000000)}, }, "[negint/8/min]": { In: withDefiniteList([]byte{1<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), Expect: List{NegInt(1)}, }, "[negint/8/max]": { In: withDefiniteList([]byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}), Expect: List{NegInt(0xffffffff_ffffffff)}, }, "[true]": { In: withDefiniteList([]byte{7<<5 | major7True}), Expect: List{Bool(true)}, }, "[false]": { In: withDefiniteList([]byte{7<<5 | major7False}), Expect: List{Bool(false)}, }, "[null]": { In: withDefiniteList([]byte{7<<5 | major7Nil}), Expect: List{&Nil{}}, }, "[undefined]": { In: withDefiniteList([]byte{7<<5 | major7Undefined}), Expect: List{&Undefined{}}, }, "[float16/+Inf]": { In: withDefiniteList([]byte{7<<5 | major7Float16, 0x7c, 0}), Expect: List{Float32(math.Float32frombits(0x7f800000))}, }, "[float16/-Inf]": { In: withDefiniteList([]byte{7<<5 | major7Float16, 0xfc, 0}), Expect: List{Float32(math.Float32frombits(0xff800000))}, }, "[float16/NaN/MSB]": { In: withDefiniteList([]byte{7<<5 | major7Float16, 0x7e, 0}), Expect: List{Float32(math.Float32frombits(0x7fc00000))}, }, "[float16/NaN/LSB]": { In: withDefiniteList([]byte{7<<5 | major7Float16, 0x7c, 1}), Expect: List{Float32(math.Float32frombits(0x7f802000))}, }, "[float32]": { In: withDefiniteList([]byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}), Expect: List{Float32(math.Float32frombits(0x7f800000))}, }, "[float64]": { In: withDefiniteList([]byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}), Expect: List{Float64(math.Float64frombits(0x7ff00000_00000000))}, }, "[_ uint/0/min]": { In: withIndefiniteList([]byte{0<<5 | 0}), Expect: List{Uint(0)}, }, "[_ uint/0/max]": { In: withIndefiniteList([]byte{0<<5 | 23}), Expect: List{Uint(23)}, }, "[_ uint/1/min]": { In: withIndefiniteList([]byte{0<<5 | 24, 0}), Expect: List{Uint(0)}, }, "[_ uint/1/max]": { In: withIndefiniteList([]byte{0<<5 | 24, 0xff}), Expect: List{Uint(0xff)}, }, "[_ uint/2/min]": { In: withIndefiniteList([]byte{0<<5 | 25, 0, 0}), Expect: List{Uint(0)}, }, "[_ uint/2/max]": { In: withIndefiniteList([]byte{0<<5 | 25, 0xff, 0xff}), Expect: List{Uint(0xffff)}, }, "[_ uint/4/min]": { In: withIndefiniteList([]byte{0<<5 | 26, 0, 0, 0, 0}), Expect: List{Uint(0)}, }, "[_ uint/4/max]": { In: withIndefiniteList([]byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}), Expect: List{Uint(0xffffffff)}, }, "[_ uint/8/min]": { In: withIndefiniteList([]byte{0<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), Expect: List{Uint(0)}, }, "[_ uint/8/max]": { In: withIndefiniteList([]byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), Expect: List{Uint(0xffffffff_ffffffff)}, }, "[_ negint/0/min]": { In: withIndefiniteList([]byte{1<<5 | 0}), Expect: List{NegInt(1)}, }, "[_ negint/0/max]": { In: withIndefiniteList([]byte{1<<5 | 23}), Expect: List{NegInt(24)}, }, "[_ negint/1/min]": { In: withIndefiniteList([]byte{1<<5 | 24, 0}), Expect: List{NegInt(1)}, }, "[_ negint/1/max]": { In: withIndefiniteList([]byte{1<<5 | 24, 0xff}), Expect: List{NegInt(0x100)}, }, "[_ negint/2/min]": { In: withIndefiniteList([]byte{1<<5 | 25, 0, 0}), Expect: List{NegInt(1)}, }, "[_ negint/2/max]": { In: withIndefiniteList([]byte{1<<5 | 25, 0xff, 0xff}), Expect: List{NegInt(0x10000)}, }, "[_ negint/4/min]": { In: withIndefiniteList([]byte{1<<5 | 26, 0, 0, 0, 0}), Expect: List{NegInt(1)}, }, "[_ negint/4/max]": { In: withIndefiniteList([]byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}), Expect: List{NegInt(0x100000000)}, }, "[_ negint/8/min]": { In: withIndefiniteList([]byte{1<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), Expect: List{NegInt(1)}, }, "[_ negint/8/max]": { In: withIndefiniteList([]byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}), Expect: List{NegInt(0xffffffff_ffffffff)}, }, "[_ true]": { In: withIndefiniteList([]byte{7<<5 | major7True}), Expect: List{Bool(true)}, }, "[_ false]": { In: withIndefiniteList([]byte{7<<5 | major7False}), Expect: List{Bool(false)}, }, "[_ null]": { In: withIndefiniteList([]byte{7<<5 | major7Nil}), Expect: List{&Nil{}}, }, "[_ undefined]": { In: withIndefiniteList([]byte{7<<5 | major7Undefined}), Expect: List{&Undefined{}}, }, "[_ float16/+Inf]": { In: withIndefiniteList([]byte{7<<5 | major7Float16, 0x7c, 0}), Expect: List{Float32(math.Float32frombits(0x7f800000))}, }, "[_ float16/-Inf]": { In: withIndefiniteList([]byte{7<<5 | major7Float16, 0xfc, 0}), Expect: List{Float32(math.Float32frombits(0xff800000))}, }, "[_ float16/NaN/MSB]": { In: withIndefiniteList([]byte{7<<5 | major7Float16, 0x7e, 0}), Expect: List{Float32(math.Float32frombits(0x7fc00000))}, }, "[_ float16/NaN/LSB]": { In: withIndefiniteList([]byte{7<<5 | major7Float16, 0x7c, 1}), Expect: List{Float32(math.Float32frombits(0x7f802000))}, }, "[_ float32]": { In: withIndefiniteList([]byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}), Expect: List{Float32(math.Float32frombits(0x7f800000))}, }, "[_ float64]": { In: withIndefiniteList([]byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}), Expect: List{Float64(math.Float64frombits(0x7ff00000_00000000))}, }, } { t.Run(name, func(t *testing.T) { actual, n, err := decode(c.In) if err != nil { t.Errorf("expect no err, got %v", err) } if n != len(c.In) { t.Errorf("didn't decode whole buffer (decoded %d of %d)", n, len(c.In)) } assertValue(t, c.Expect, actual) }) } } func TestDecode_Map(t *testing.T) { for name, c := range map[string]struct { In []byte Expect Value }{ "{uint/0/min}": { In: withDefiniteMap([]byte{0<<5 | 0}), Expect: Map{"foo": Uint(0)}, }, "{uint/0/max}": { In: withDefiniteMap([]byte{0<<5 | 23}), Expect: Map{"foo": Uint(23)}, }, "{uint/1/min}": { In: withDefiniteMap([]byte{0<<5 | 24, 0}), Expect: Map{"foo": Uint(0)}, }, "{uint/1/max}": { In: withDefiniteMap([]byte{0<<5 | 24, 0xff}), Expect: Map{"foo": Uint(0xff)}, }, "{uint/2/min}": { In: withDefiniteMap([]byte{0<<5 | 25, 0, 0}), Expect: Map{"foo": Uint(0)}, }, "{uint/2/max}": { In: withDefiniteMap([]byte{0<<5 | 25, 0xff, 0xff}), Expect: Map{"foo": Uint(0xffff)}, }, "{uint/4/min}": { In: withDefiniteMap([]byte{0<<5 | 26, 0, 0, 0, 0}), Expect: Map{"foo": Uint(0)}, }, "{uint/4/max}": { In: withDefiniteMap([]byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}), Expect: Map{"foo": Uint(0xffffffff)}, }, "{uint/8/min}": { In: withDefiniteMap([]byte{0<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), Expect: Map{"foo": Uint(0)}, }, "{uint/8/max}": { In: withDefiniteMap([]byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), Expect: Map{"foo": Uint(0xffffffff_ffffffff)}, }, "{negint/0/min}": { In: withDefiniteMap([]byte{1<<5 | 0}), Expect: Map{"foo": NegInt(1)}, }, "{negint/0/max}": { In: withDefiniteMap([]byte{1<<5 | 23}), Expect: Map{"foo": NegInt(24)}, }, "{negint/1/min}": { In: withDefiniteMap([]byte{1<<5 | 24, 0}), Expect: Map{"foo": NegInt(1)}, }, "{negint/1/max}": { In: withDefiniteMap([]byte{1<<5 | 24, 0xff}), Expect: Map{"foo": NegInt(0x100)}, }, "{negint/2/min}": { In: withDefiniteMap([]byte{1<<5 | 25, 0, 0}), Expect: Map{"foo": NegInt(1)}, }, "{negint/2/max}": { In: withDefiniteMap([]byte{1<<5 | 25, 0xff, 0xff}), Expect: Map{"foo": NegInt(0x10000)}, }, "{negint/4/min}": { In: withDefiniteMap([]byte{1<<5 | 26, 0, 0, 0, 0}), Expect: Map{"foo": NegInt(1)}, }, "{negint/4/max}": { In: withDefiniteMap([]byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}), Expect: Map{"foo": NegInt(0x100000000)}, }, "{negint/8/min}": { In: withDefiniteMap([]byte{1<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), Expect: Map{"foo": NegInt(1)}, }, "{negint/8/max}": { In: withDefiniteMap([]byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}), Expect: Map{"foo": NegInt(0xffffffff_ffffffff)}, }, "{true}": { In: withDefiniteMap([]byte{7<<5 | major7True}), Expect: Map{"foo": Bool(true)}, }, "{false}": { In: withDefiniteMap([]byte{7<<5 | major7False}), Expect: Map{"foo": Bool(false)}, }, "{null}": { In: withDefiniteMap([]byte{7<<5 | major7Nil}), Expect: Map{"foo": &Nil{}}, }, "{undefined}": { In: withDefiniteMap([]byte{7<<5 | major7Undefined}), Expect: Map{"foo": &Undefined{}}, }, "{float16/+Inf}": { In: withDefiniteMap([]byte{7<<5 | major7Float16, 0x7c, 0}), Expect: Map{"foo": Float32(math.Float32frombits(0x7f800000))}, }, "{float16/-Inf}": { In: withDefiniteMap([]byte{7<<5 | major7Float16, 0xfc, 0}), Expect: Map{"foo": Float32(math.Float32frombits(0xff800000))}, }, "{float16/NaN/MSB}": { In: withDefiniteMap([]byte{7<<5 | major7Float16, 0x7e, 0}), Expect: Map{"foo": Float32(math.Float32frombits(0x7fc00000))}, }, "{float16/NaN/LSB}": { In: withDefiniteMap([]byte{7<<5 | major7Float16, 0x7c, 1}), Expect: Map{"foo": Float32(math.Float32frombits(0x7f802000))}, }, "{float32}": { In: withDefiniteMap([]byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}), Expect: Map{"foo": Float32(math.Float32frombits(0x7f800000))}, }, "{float64}": { In: withDefiniteMap([]byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}), Expect: Map{"foo": Float64(math.Float64frombits(0x7ff00000_00000000))}, }, "{_ uint/0/min}": { In: withIndefiniteMap([]byte{0<<5 | 0}), Expect: Map{"foo": Uint(0)}, }, "{_ uint/0/max}": { In: withIndefiniteMap([]byte{0<<5 | 23}), Expect: Map{"foo": Uint(23)}, }, "{_ uint/1/min}": { In: withIndefiniteMap([]byte{0<<5 | 24, 0}), Expect: Map{"foo": Uint(0)}, }, "{_ uint/1/max}": { In: withIndefiniteMap([]byte{0<<5 | 24, 0xff}), Expect: Map{"foo": Uint(0xff)}, }, "{_ uint/2/min}": { In: withIndefiniteMap([]byte{0<<5 | 25, 0, 0}), Expect: Map{"foo": Uint(0)}, }, "{_ uint/2/max}": { In: withIndefiniteMap([]byte{0<<5 | 25, 0xff, 0xff}), Expect: Map{"foo": Uint(0xffff)}, }, "{_ uint/4/min}": { In: withIndefiniteMap([]byte{0<<5 | 26, 0, 0, 0, 0}), Expect: Map{"foo": Uint(0)}, }, "{_ uint/4/max}": { In: withIndefiniteMap([]byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}), Expect: Map{"foo": Uint(0xffffffff)}, }, "{_ uint/8/min}": { In: withIndefiniteMap([]byte{0<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), Expect: Map{"foo": Uint(0)}, }, "{_ uint/8/max}": { In: withIndefiniteMap([]byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), Expect: Map{"foo": Uint(0xffffffff_ffffffff)}, }, "{_ negint/0/min}": { In: withIndefiniteMap([]byte{1<<5 | 0}), Expect: Map{"foo": NegInt(1)}, }, "{_ negint/0/max}": { In: withIndefiniteMap([]byte{1<<5 | 23}), Expect: Map{"foo": NegInt(24)}, }, "{_ negint/1/min}": { In: withIndefiniteMap([]byte{1<<5 | 24, 0}), Expect: Map{"foo": NegInt(1)}, }, "{_ negint/1/max}": { In: withIndefiniteMap([]byte{1<<5 | 24, 0xff}), Expect: Map{"foo": NegInt(0x100)}, }, "{_ negint/2/min}": { In: withIndefiniteMap([]byte{1<<5 | 25, 0, 0}), Expect: Map{"foo": NegInt(1)}, }, "{_ negint/2/max}": { In: withIndefiniteMap([]byte{1<<5 | 25, 0xff, 0xff}), Expect: Map{"foo": NegInt(0x10000)}, }, "{_ negint/4/min}": { In: withIndefiniteMap([]byte{1<<5 | 26, 0, 0, 0, 0}), Expect: Map{"foo": NegInt(1)}, }, "{_ negint/4/max}": { In: withIndefiniteMap([]byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}), Expect: Map{"foo": NegInt(0x100000000)}, }, "{_ negint/8/min}": { In: withIndefiniteMap([]byte{1<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), Expect: Map{"foo": NegInt(1)}, }, "{_ negint/8/max}": { In: withIndefiniteMap([]byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}), Expect: Map{"foo": NegInt(0xffffffff_ffffffff)}, }, "{_ true}": { In: withIndefiniteMap([]byte{7<<5 | major7True}), Expect: Map{"foo": Bool(true)}, }, "{_ false}": { In: withIndefiniteMap([]byte{7<<5 | major7False}), Expect: Map{"foo": Bool(false)}, }, "{_ null}": { In: withIndefiniteMap([]byte{7<<5 | major7Nil}), Expect: Map{"foo": &Nil{}}, }, "{_ undefined}": { In: withIndefiniteMap([]byte{7<<5 | major7Undefined}), Expect: Map{"foo": &Undefined{}}, }, "{_ float16/+Inf}": { In: withIndefiniteMap([]byte{7<<5 | major7Float16, 0x7c, 0}), Expect: Map{"foo": Float32(math.Float32frombits(0x7f800000))}, }, "{_ float16/-Inf}": { In: withIndefiniteMap([]byte{7<<5 | major7Float16, 0xfc, 0}), Expect: Map{"foo": Float32(math.Float32frombits(0xff800000))}, }, "{_ float16/NaN/MSB}": { In: withIndefiniteMap([]byte{7<<5 | major7Float16, 0x7e, 0}), Expect: Map{"foo": Float32(math.Float32frombits(0x7fc00000))}, }, "{_ float16/NaN/LSB}": { In: withIndefiniteMap([]byte{7<<5 | major7Float16, 0x7c, 1}), Expect: Map{"foo": Float32(math.Float32frombits(0x7f802000))}, }, "{_ float32}": { In: withIndefiniteMap([]byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}), Expect: Map{"foo": Float32(math.Float32frombits(0x7f800000))}, }, "{_ float64}": { In: withIndefiniteMap([]byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}), Expect: Map{"foo": Float64(math.Float64frombits(0x7ff00000_00000000))}, }, } { t.Run(name, func(t *testing.T) { actual, n, err := decode(c.In) if err != nil { t.Errorf("expect no err, got %v", err) } if n != len(c.In) { t.Errorf("didn't decode whole buffer (decoded %d of %d)", n, len(c.In)) } assertValue(t, c.Expect, actual) }) } } func TestDecode_Tag(t *testing.T) { for name, c := range map[string]struct { In []byte Expect Value }{ "0/min": { In: []byte{6<<5 | 0, 1}, Expect: &Tag{0, Uint(1)}, }, "0/max": { In: []byte{6<<5 | 23, 1}, Expect: &Tag{23, Uint(1)}, }, "1/min": { In: []byte{6<<5 | 24, 0, 1}, Expect: &Tag{0, Uint(1)}, }, "1/max": { In: []byte{6<<5 | 24, 0xff, 1}, Expect: &Tag{0xff, Uint(1)}, }, "2/min": { In: []byte{6<<5 | 25, 0, 0, 1}, Expect: &Tag{0, Uint(1)}, }, "2/max": { In: []byte{6<<5 | 25, 0xff, 0xff, 1}, Expect: &Tag{0xffff, Uint(1)}, }, "4/min": { In: []byte{6<<5 | 26, 0, 0, 0, 0, 1}, Expect: &Tag{0, Uint(1)}, }, "4/max": { In: []byte{6<<5 | 26, 0xff, 0xff, 0xff, 0xff, 1}, Expect: &Tag{0xffffffff, Uint(1)}, }, "8/min": { In: []byte{6<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Expect: &Tag{0, Uint(1)}, }, "8/max": { In: []byte{6<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 1}, Expect: &Tag{0xffffffff_ffffffff, Uint(1)}, }, } { t.Run(name, func(t *testing.T) { actual, n, err := decode(c.In) if err != nil { t.Errorf("expect no err, got %v", err) } if n != len(c.In) { t.Errorf("didn't decode whole buffer (decoded %d of %d)", n, len(c.In)) } assertValue(t, c.Expect, actual) }) } } func assertValue(t *testing.T, e, a Value) { switch v := e.(type) { case Uint, NegInt, Slice, String, Bool, *Nil, *Undefined: if !reflect.DeepEqual(e, a) { t.Errorf("%v != %v", e, a) } case List: assertList(t, v, a) case Map: assertMap(t, v, a) case *Tag: assertTag(t, v, a) case Float32: assertMajor7Float32(t, v, a) case Float64: assertMajor7Float64(t, v, a) default: t.Errorf("unrecognized variant %T", e) } } func assertList(t *testing.T, e List, a Value) { av, ok := a.(List) if !ok { t.Errorf("%T != %T", e, a) return } if len(e) != len(av) { t.Errorf("length %d != %d", len(e), len(av)) return } for i := 0; i < len(e); i++ { assertValue(t, e[i], av[i]) } } func assertMap(t *testing.T, e Map, a Value) { av, ok := a.(Map) if !ok { t.Errorf("%T != %T", e, a) return } if len(e) != len(av) { t.Errorf("length %d != %d", len(e), len(av)) return } for k, ev := range e { avv, ok := av[k] if !ok { t.Errorf("missing key %s", k) return } assertValue(t, ev, avv) } } func assertTag(t *testing.T, e *Tag, a Value) { av, ok := a.(*Tag) if !ok { t.Errorf("%T != %T", e, a) return } if e.ID != av.ID { t.Errorf("tag ID %d != %d", e.ID, av.ID) return } assertValue(t, e.Value, av.Value) } func assertMajor7Float32(t *testing.T, e Float32, a Value) { av, ok := a.(Float32) if !ok { t.Errorf("%T != %T", e, a) return } if math.Float32bits(float32(e)) != math.Float32bits(float32(av)) { t.Errorf("float32(%x) != float32(%x)", e, av) } } func assertMajor7Float64(t *testing.T, e Float64, a Value) { av, ok := a.(Float64) if !ok { t.Errorf("%T != %T", e, a) return } if math.Float64bits(float64(e)) != math.Float64bits(float64(av)) { t.Errorf("float64(%x) != float64(%x)", e, av) } } var mapKeyFoo = []byte{0x63, 0x66, 0x6f, 0x6f} func withDefiniteList(p []byte) []byte { return append([]byte{4<<5 | 1}, p...) } func withIndefiniteList(p []byte) []byte { p = append([]byte{4<<5 | 31}, p...) return append(p, 0xff) } func withDefiniteMap(p []byte) []byte { head := append([]byte{5<<5 | 1}, mapKeyFoo...) return append(head, p...) } func withIndefiniteMap(p []byte) []byte { head := append([]byte{5<<5 | 31}, mapKeyFoo...) p = append(head, p...) return append(p, 0xff) } smithy-go-1.20.3/encoding/cbor/encode.go000066400000000000000000000106501463735525100200310ustar00rootroot00000000000000package cbor import ( "encoding/binary" "math" ) func (i Uint) len() int { return itoarglen(uint64(i)) } func (i Uint) encode(p []byte) int { return encodeArg(majorTypeUint, uint64(i), p) } func (i NegInt) len() int { return itoarglen(uint64(i) - 1) } func (i NegInt) encode(p []byte) int { return encodeArg(majorTypeNegInt, uint64(i-1), p) } func (s Slice) len() int { return itoarglen(len(s)) + len(s) } func (s Slice) encode(p []byte) int { off := encodeArg(majorTypeSlice, len(s), p) copy(p[off:], []byte(s)) return off + len(s) } func (s String) len() int { return itoarglen(len(s)) + len(s) } func (s String) encode(p []byte) int { off := encodeArg(majorTypeString, len(s), p) copy(p[off:], []byte(s)) return off + len(s) } func (l List) len() int { total := itoarglen(len(l)) for _, v := range l { total += v.len() } return total } func (l List) encode(p []byte) int { off := encodeArg(majorTypeList, len(l), p) for _, v := range l { off += v.encode(p[off:]) } return off } func (m Map) len() int { total := itoarglen(len(m)) for k, v := range m { total += String(k).len() + v.len() } return total } func (m Map) encode(p []byte) int { off := encodeArg(majorTypeMap, len(m), p) for k, v := range m { off += String(k).encode(p[off:]) off += v.encode(p[off:]) } return off } func (t Tag) len() int { return itoarglen(t.ID) + t.Value.len() } func (t Tag) encode(p []byte) int { off := encodeArg(majorTypeTag, t.ID, p) return off + t.Value.encode(p[off:]) } func (b Bool) len() int { return 1 } func (b Bool) encode(p []byte) int { if b { p[0] = compose(majorType7, major7True) } else { p[0] = compose(majorType7, major7False) } return 1 } func (*Nil) len() int { return 1 } func (*Nil) encode(p []byte) int { p[0] = compose(majorType7, major7Nil) return 1 } func (*Undefined) len() int { return 1 } func (*Undefined) encode(p []byte) int { p[0] = compose(majorType7, major7Undefined) return 1 } func (f Float32) len() int { return 5 } func (f Float32) encode(p []byte) int { p[0] = compose(majorType7, major7Float32) binary.BigEndian.PutUint32(p[1:], math.Float32bits(float32(f))) return 5 } func (f Float64) len() int { return 9 } func (f Float64) encode(p []byte) int { p[0] = compose(majorType7, major7Float64) binary.BigEndian.PutUint64(p[1:], math.Float64bits(float64(f))) return 9 } func compose(major majorType, minor byte) byte { return byte(major)<<5 | minor } func itoarglen[I int | uint64](v I) int { vv := uint64(v) if vv < 24 { return 1 // type and len in single byte } else if vv < 0x100 { return 2 // type + 1-byte len } else if vv < 0x10000 { return 3 // type + 2-byte len } else if vv < 0x100000000 { return 5 // type + 4-byte len } return 9 // type + 8-byte len } func encodeArg[I int | uint64](t majorType, arg I, p []byte) int { aarg := uint64(arg) if aarg < 24 { p[0] = byte(t)<<5 | byte(aarg) return 1 } else if aarg < 0x100 { p[0] = compose(t, minorArg1) p[1] = byte(aarg) return 2 } else if aarg < 0x10000 { p[0] = compose(t, minorArg2) binary.BigEndian.PutUint16(p[1:], uint16(aarg)) return 3 } else if aarg < 0x100000000 { p[0] = compose(t, minorArg4) binary.BigEndian.PutUint32(p[1:], uint32(aarg)) return 5 } p[0] = compose(t, minorArg8) binary.BigEndian.PutUint64(p[1:], uint64(aarg)) return 9 } // EncodeRaw encodes opaque CBOR data. // // This is used by the encoder for the purpose of embedding document shapes. // Decode will never return values of this type. type EncodeRaw []byte func (v EncodeRaw) len() int { return len(v) } func (v EncodeRaw) encode(p []byte) int { copy(p, v) return len(v) } // FixedUint encodes fixed-width Uint values. // // This is used by the encoder for the purpose of embedding integrals in // document shapes. Decode will never return values of this type. type EncodeFixedUint uint64 func (EncodeFixedUint) len() int { return 9 } func (v EncodeFixedUint) encode(p []byte) int { p[0] = compose(majorTypeUint, minorArg8) binary.BigEndian.PutUint64(p[1:], uint64(v)) return 9 } // FixedUint encodes fixed-width NegInt values. // // This is used by the encoder for the purpose of embedding integrals in // document shapes. Decode will never return values of this type. type EncodeFixedNegInt uint64 func (EncodeFixedNegInt) len() int { return 9 } func (v EncodeFixedNegInt) encode(p []byte) int { p[0] = compose(majorTypeNegInt, minorArg8) binary.BigEndian.PutUint64(p[1:], uint64(v-1)) return 9 } smithy-go-1.20.3/encoding/cbor/encode_test.go000066400000000000000000000253421463735525100210740ustar00rootroot00000000000000package cbor import ( "bytes" "encoding/hex" "math" "testing" ) func TestEncode_Atomic(t *testing.T) { for name, c := range map[string]struct { Expect []byte In Value }{ "uint/0/min": { []byte{0<<5 | 0}, Uint(0), }, "uint/0/max": { []byte{0<<5 | 23}, Uint(23), }, "uint/1/min": { []byte{0<<5 | 24, 24}, Uint(24), }, "uint/1/max": { []byte{0<<5 | 24, 0xff}, Uint(0xff), }, "uint/2/min": { []byte{0<<5 | 25, 1, 0}, Uint(0x100), }, "uint/2/max": { []byte{0<<5 | 25, 0xff, 0xff}, Uint(0xffff), }, "uint/4/min": { []byte{0<<5 | 26, 1, 0, 0, 0}, Uint(0x1000000), }, "uint/4/max": { []byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}, Uint(0xffffffff), }, "uint/8/min": { []byte{0<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0}, Uint(0x1000000_00000000), }, "uint/8/max": { []byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, Uint(0xffffffff_ffffffff), }, "negint/0/min": { []byte{1<<5 | 0}, NegInt(1), }, "negint/0/max": { []byte{1<<5 | 23}, NegInt(24), }, "negint/1/min": { []byte{1<<5 | 24, 24}, NegInt(25), }, "negint/1/max": { []byte{1<<5 | 24, 0xff}, NegInt(0x100), }, "negint/2/min": { []byte{1<<5 | 25, 1, 0}, NegInt(0x101), }, "negint/2/max": { []byte{1<<5 | 25, 0xff, 0xff}, NegInt(0x10000), }, "negint/4/min": { []byte{1<<5 | 26, 1, 0, 0, 0}, NegInt(0x1000001), }, "negint/4/max": { []byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}, NegInt(0x100000000), }, "negint/8/min": { []byte{1<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0}, NegInt(0x1000000_00000001), }, "negint/8/max": { []byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}, NegInt(0xffffffff_ffffffff), }, "true": { []byte{7<<5 | major7True}, Bool(true), }, "false": { []byte{7<<5 | major7False}, Bool(false), }, "null": { []byte{7<<5 | major7Nil}, &Nil{}, }, "undefined": { []byte{7<<5 | major7Undefined}, &Undefined{}, }, "float32": { []byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}, Float32(math.Float32frombits(0x7f800000)), }, "float64": { []byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}, Float64(math.Float64frombits(0x7ff00000_00000000)), }, } { t.Run(name, func(t *testing.T) { actual := Encode(c.In) if !bytes.Equal(c.Expect, actual) { t.Errorf("bytes not equal (%s != %s)", hex.EncodeToString(c.Expect), hex.EncodeToString(actual)) } }) } } func TestEncode_Slice(t *testing.T) { for name, c := range map[string]struct { Expect []byte In Value }{ "len = 0": { []byte{2<<5 | 0}, Slice{}, }, "len > 0": { []byte{2<<5 | 3, 0x66, 0x6f, 0x6f}, Slice{0x66, 0x6f, 0x6f}, }, } { t.Run(name, func(t *testing.T) { actual := Encode(c.In) if !bytes.Equal(c.Expect, actual) { t.Errorf("bytes not equal (%s != %s)", hex.EncodeToString(c.Expect), hex.EncodeToString(actual)) } }) } } func TestEncode_String(t *testing.T) { for name, c := range map[string]struct { Expect []byte In Value }{ "len = 0": { []byte{3<<5 | 0}, String(""), }, "len > 0": { []byte{3<<5 | 3, 0x66, 0x6f, 0x6f}, String("foo"), }, } { t.Run(name, func(t *testing.T) { actual := Encode(c.In) if !bytes.Equal(c.Expect, actual) { t.Errorf("bytes not equal (%s != %s)", hex.EncodeToString(c.Expect), hex.EncodeToString(actual)) } }) } } func TestEncode_List(t *testing.T) { for name, c := range map[string]struct { Expect []byte In Value }{ "[uint/0/min]": { withDefiniteList([]byte{0<<5 | 0}), List{Uint(0)}, }, "[uint/0/max]": { withDefiniteList([]byte{0<<5 | 23}), List{Uint(23)}, }, "[uint/1/min]": { withDefiniteList([]byte{0<<5 | 24, 24}), List{Uint(24)}, }, "[uint/1/max]": { withDefiniteList([]byte{0<<5 | 24, 0xff}), List{Uint(0xff)}, }, "[uint/2/min]": { withDefiniteList([]byte{0<<5 | 25, 1, 0}), List{Uint(0x100)}, }, "[uint/2/max]": { withDefiniteList([]byte{0<<5 | 25, 0xff, 0xff}), List{Uint(0xffff)}, }, "[uint/4/min]": { withDefiniteList([]byte{0<<5 | 26, 1, 0, 0, 0}), List{Uint(0x1000000)}, }, "[uint/4/max]": { withDefiniteList([]byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}), List{Uint(0xffffffff)}, }, "[uint/8/min]": { withDefiniteList([]byte{0<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0}), List{Uint(0x1000000_00000000)}, }, "[uint/8/max]": { withDefiniteList([]byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), List{Uint(0xffffffff_ffffffff)}, }, "[negint/0/min]": { withDefiniteList([]byte{1<<5 | 0}), List{NegInt(1)}, }, "[negint/0/max]": { withDefiniteList([]byte{1<<5 | 23}), List{NegInt(24)}, }, "[negint/1/min]": { withDefiniteList([]byte{1<<5 | 24, 24}), List{NegInt(25)}, }, "[negint/1/max]": { withDefiniteList([]byte{1<<5 | 24, 0xff}), List{NegInt(0x100)}, }, "[negint/2/min]": { withDefiniteList([]byte{1<<5 | 25, 1, 0}), List{NegInt(0x101)}, }, "[negint/2/max]": { withDefiniteList([]byte{1<<5 | 25, 0xff, 0xff}), List{NegInt(0x10000)}, }, "[negint/4/min]": { withDefiniteList([]byte{1<<5 | 26, 1, 0, 0, 0}), List{NegInt(0x1000001)}, }, "[negint/4/max]": { withDefiniteList([]byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}), List{NegInt(0x100000000)}, }, "[negint/8/min]": { withDefiniteList([]byte{1<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0}), List{NegInt(0x1000000_00000001)}, }, "[negint/8/max]": { withDefiniteList([]byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}), List{NegInt(0xffffffff_ffffffff)}, }, "[true]": { withDefiniteList([]byte{7<<5 | major7True}), List{Bool(true)}, }, "[false]": { withDefiniteList([]byte{7<<5 | major7False}), List{Bool(false)}, }, "[null]": { withDefiniteList([]byte{7<<5 | major7Nil}), List{&Nil{}}, }, "[undefined]": { withDefiniteList([]byte{7<<5 | major7Undefined}), List{&Undefined{}}, }, "[float32]": { withDefiniteList([]byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}), List{Float32(math.Float32frombits(0x7f800000))}, }, "[float64]": { withDefiniteList([]byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}), List{Float64(math.Float64frombits(0x7ff00000_00000000))}, }, } { t.Run(name, func(t *testing.T) { actual := Encode(c.In) if !bytes.Equal(c.Expect, actual) { t.Errorf("bytes not equal (%s != %s)", hex.EncodeToString(c.Expect), hex.EncodeToString(actual)) } }) } } func TestEncode_Map(t *testing.T) { for name, c := range map[string]struct { Expect []byte In Value }{ "{uint/0/min}": { withDefiniteMap([]byte{0<<5 | 0}), Map{"foo": Uint(0)}, }, "{uint/0/max}": { withDefiniteMap([]byte{0<<5 | 23}), Map{"foo": Uint(23)}, }, "{uint/1/min}": { withDefiniteMap([]byte{0<<5 | 24, 24}), Map{"foo": Uint(24)}, }, "{uint/1/max}": { withDefiniteMap([]byte{0<<5 | 24, 0xff}), Map{"foo": Uint(0xff)}, }, "{uint/2/min}": { withDefiniteMap([]byte{0<<5 | 25, 1, 0}), Map{"foo": Uint(0x100)}, }, "{uint/2/max}": { withDefiniteMap([]byte{0<<5 | 25, 0xff, 0xff}), Map{"foo": Uint(0xffff)}, }, "{uint/4/min}": { withDefiniteMap([]byte{0<<5 | 26, 1, 0, 0, 0}), Map{"foo": Uint(0x1000000)}, }, "{uint/4/max}": { withDefiniteMap([]byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}), Map{"foo": Uint(0xffffffff)}, }, "{uint/8/min}": { withDefiniteMap([]byte{0<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0}), Map{"foo": Uint(0x1000000_00000000)}, }, "{uint/8/max}": { withDefiniteMap([]byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), Map{"foo": Uint(0xffffffff_ffffffff)}, }, "{negint/0/min}": { withDefiniteMap([]byte{1<<5 | 0}), Map{"foo": NegInt(1)}, }, "{negint/0/max}": { withDefiniteMap([]byte{1<<5 | 23}), Map{"foo": NegInt(24)}, }, "{negint/1/min}": { withDefiniteMap([]byte{1<<5 | 24, 24}), Map{"foo": NegInt(25)}, }, "{negint/1/max}": { withDefiniteMap([]byte{1<<5 | 24, 0xff}), Map{"foo": NegInt(0x100)}, }, "{negint/2/min}": { withDefiniteMap([]byte{1<<5 | 25, 1, 0}), Map{"foo": NegInt(0x101)}, }, "{negint/2/max}": { withDefiniteMap([]byte{1<<5 | 25, 0xff, 0xff}), Map{"foo": NegInt(0x10000)}, }, "{negint/4/min}": { withDefiniteMap([]byte{1<<5 | 26, 1, 0, 0, 0}), Map{"foo": NegInt(0x1000001)}, }, "{negint/4/max}": { withDefiniteMap([]byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}), Map{"foo": NegInt(0x100000000)}, }, "{negint/8/min}": { withDefiniteMap([]byte{1<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0}), Map{"foo": NegInt(0x1000000_00000001)}, }, "{negint/8/max}": { withDefiniteMap([]byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}), Map{"foo": NegInt(0xffffffff_ffffffff)}, }, "{true}": { withDefiniteMap([]byte{7<<5 | major7True}), Map{"foo": Bool(true)}, }, "{false}": { withDefiniteMap([]byte{7<<5 | major7False}), Map{"foo": Bool(false)}, }, "{null}": { withDefiniteMap([]byte{7<<5 | major7Nil}), Map{"foo": &Nil{}}, }, "{undefined}": { withDefiniteMap([]byte{7<<5 | major7Undefined}), Map{"foo": &Undefined{}}, }, "{float32}": { withDefiniteMap([]byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}), Map{"foo": Float32(math.Float32frombits(0x7f800000))}, }, "{float64}": { withDefiniteMap([]byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}), Map{"foo": Float64(math.Float64frombits(0x7ff00000_00000000))}, }, } { t.Run(name, func(t *testing.T) { actual := Encode(c.In) if !bytes.Equal(c.Expect, actual) { t.Errorf("bytes not equal (%s != %s)", hex.EncodeToString(c.Expect), hex.EncodeToString(actual)) } }) } } func TestEncode_Tag(t *testing.T) { for name, c := range map[string]struct { Expect []byte In Value }{ "0/min": { []byte{6<<5 | 0, 1}, &Tag{0, Uint(1)}, }, "0/max": { []byte{6<<5 | 23, 1}, &Tag{23, Uint(1)}, }, "1/min": { []byte{6<<5 | 24, 24, 1}, &Tag{24, Uint(1)}, }, "1/max": { []byte{6<<5 | 24, 0xff, 1}, &Tag{0xff, Uint(1)}, }, "2/min": { []byte{6<<5 | 25, 1, 0, 1}, &Tag{0x100, Uint(1)}, }, "2/max": { []byte{6<<5 | 25, 0xff, 0xff, 1}, &Tag{0xffff, Uint(1)}, }, "4/min": { []byte{6<<5 | 26, 1, 0, 0, 0, 1}, &Tag{0x1000000, Uint(1)}, }, "4/max": { []byte{6<<5 | 26, 0xff, 0xff, 0xff, 0xff, 1}, &Tag{0xffffffff, Uint(1)}, }, "8/min": { []byte{6<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0, 1}, &Tag{0x1000000_00000000, Uint(1)}, }, "8/max": { []byte{6<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 1}, &Tag{0xffffffff_ffffffff, Uint(1)}, }, } { t.Run(name, func(t *testing.T) { actual := Encode(c.In) if !bytes.Equal(c.Expect, actual) { t.Errorf("bytes not equal (%s != %s)", hex.EncodeToString(c.Expect), hex.EncodeToString(actual)) } }) } } smithy-go-1.20.3/encoding/cbor/float16.go000066400000000000000000000027301463735525100200500ustar00rootroot00000000000000package cbor func float16to32(f uint16) uint32 { sign, exp, mant := splitf16(f) if exp == 0x1f { return sign | 0xff<<23 | mant // infinity/NaN } if exp == 0 { // subnormal if mant == 0 { return sign } return normalize(sign, mant) } return sign | (exp+127-15)<<23 | mant // rebias exp by the difference between the two } func splitf16(f uint16) (sign, exp, mantissa uint32) { const smask = 0x1 << 15 // put sign in float32 position const emask = 0x1f << 10 // pull exponent as a number (for bias shift) const mmask = 0x3ff // put mantissa in float32 position return uint32(f&smask) << 16, uint32(f&emask) >> 10, uint32(f&mmask) << 13 } // moves a float16 normal into normal float32 space // to do this we must re-express the float16 mantissa in terms of a normal // float32 where the hidden bit is 1, e.g. // // f16: 0 00000 0001010000 = 0.000101 * 2^(-14), which is equal to // f32: 0 01101101 01000000000000000000000 = 1.01 * 2^(-18) // // this is achieved by shifting the mantissa to the right until the leading bit // that == 1 reaches position 24, then the number of positions shifted over is // equal to the offset from the subnormal exponent func normalize(sign, mant uint32) uint32 { exp := uint32(-14 + 127) // f16 subnormal exp, with f32 bias for mant&0x800000 == 0 { // repeat until bit 24 ("hidden" mantissa) is 1 mant <<= 1 exp-- // tracking the offset } mant &= 0x7fffff // remask to 23bit return sign | exp<<23 | mant } smithy-go-1.20.3/encoding/cbor/float16_test.go000066400000000000000000000013771463735525100211150ustar00rootroot00000000000000package cbor import ( "testing" ) func TestFloat16To32(t *testing.T) { for name, c := range map[string]struct { In uint16 Expect uint32 }{ "+infinity": { 0b0_11111_0000000000, 0b0_11111111_00000000000000000000000, }, "-infinity": { 0b1_11111_0000000000, 0b1_11111111_00000000000000000000000, }, "NaN": { 0b0_11111_0101010101, 0b0_11111111_01010101010000000000000, }, "absolute zero": {0, 0}, "subnormal": { 0b0_00000_0001010000, 0b0_01101101_01000000000000000000000, }, "normal": { 0b0_00001_0001010000, 0b0_0001110001_00010100000000000000000, }, } { t.Run(name, func(t *testing.T) { if actual := float16to32(c.In); c.Expect != actual { t.Errorf("%x != %x", c.Expect, actual) } }) } } smithy-go-1.20.3/encoding/cbor/fuzz_test.go000066400000000000000000000040601463735525100206270ustar00rootroot00000000000000//go:build fuzz // +build fuzz package cbor import ( "crypto/rand" "encoding/hex" "fmt" "testing" ) // caught by fuzz: // - broken typecast from uint64 to int when checking encoded string(mt2,3) length vs buflen // - huge encoded list/map sizes would cause panics on make() // - map declaration at end of buffer would attempt to peek p[0] when len(p) == 0 func TestDecode_Fuzz(t *testing.T) { const runs = 1_000_000 const buflen = 512 p := make([]byte, buflen) defer func() { if err := recover(); err != nil { fmt.Println(hex.EncodeToString(p)) dump(p) t.Fatalf("decode panic: %v\n", err) } }() for i := 0; i < runs; i++ { if _, err := rand.Read(p); err != nil { t.Fatalf("create randbuf: %v", err) } decode(p) } } func dump(p []byte) { for len(p) > 0 { var off int major, minor := peekMajor(p), peekMinor(p) switch major { case majorTypeUint, majorTypeNegInt, majorType7: if minor > 27 { fmt.Printf("%d, %d (invalid)\n", major, minor) return } arg, n, err := decodeArgument(p) if err != nil { panic(err) } fmt.Printf("%d, %d\n", major, arg) off = n case majorTypeSlice, majorTypeString: if minor == 31 { panic("todo") } else if minor > 27 { fmt.Printf("%d, %d (invalid)\n", major, minor) return } arg, n, err := decodeArgument(p) if err != nil { panic(err) } fmt.Printf("str(%d), len %d\n", major, arg) off = n + int(arg) case majorTypeList, majorTypeMap: if minor == 31 { panic("todo") } else if minor > 27 { fmt.Printf("%d, %d (invalid)\n", major, minor) return } arg, n, err := decodeArgument(p) if err != nil { panic(err) } fmt.Printf("container(%d), len %d\n", major, arg) off = n case majorTypeTag: if minor > 27 { fmt.Printf("tag, %d (invalid)\n", minor) return } arg, n, err := decodeArgument(p) if err != nil { panic(err) } fmt.Printf("tag, %d\n", arg) off = n } if off > len(p) { fmt.Println("overflow, stop") return } p = p[off:] } fmt.Println("EOF") } smithy-go-1.20.3/encoding/doc.go000066400000000000000000000001611463735525100164100ustar00rootroot00000000000000// Package encoding provides utilities for encoding values for specific // document encodings. package encoding smithy-go-1.20.3/encoding/encoding.go000066400000000000000000000017271463735525100174420ustar00rootroot00000000000000package encoding import ( "fmt" "math" "strconv" ) // EncodeFloat encodes a float value as per the stdlib encoder for json and xml protocol // This encodes a float value into dst while attempting to conform to ES6 ToString for Numbers // // Based on encoding/json floatEncoder from the Go Standard Library // https://golang.org/src/encoding/json/encode.go func EncodeFloat(dst []byte, v float64, bits int) []byte { if math.IsInf(v, 0) || math.IsNaN(v) { panic(fmt.Sprintf("invalid float value: %s", strconv.FormatFloat(v, 'g', -1, bits))) } abs := math.Abs(v) fmt := byte('f') if abs != 0 { if bits == 64 && (abs < 1e-6 || abs >= 1e21) || bits == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) { fmt = 'e' } } dst = strconv.AppendFloat(dst, v, fmt, -1, bits) if fmt == 'e' { // clean up e-09 to e-9 n := len(dst) if n >= 4 && dst[n-4] == 'e' && dst[n-3] == '-' && dst[n-2] == '0' { dst[n-2] = dst[n-1] dst = dst[:n-1] } } return dst } smithy-go-1.20.3/encoding/httpbinding/000077500000000000000000000000001463735525100176305ustar00rootroot00000000000000smithy-go-1.20.3/encoding/httpbinding/encode.go000066400000000000000000000071461463735525100214240ustar00rootroot00000000000000package httpbinding import ( "fmt" "net/http" "net/url" "strconv" "strings" ) const ( contentLengthHeader = "Content-Length" floatNaN = "NaN" floatInfinity = "Infinity" floatNegInfinity = "-Infinity" ) // An Encoder provides encoding of REST URI path, query, and header components // of an HTTP request. Can also encode a stream as the payload. // // Does not support SetFields. type Encoder struct { path, rawPath, pathBuffer []byte query url.Values header http.Header } // NewEncoder creates a new encoder from the passed in request. It assumes that // raw path contains no valuable information at this point, so it passes in path // as path and raw path for subsequent trans func NewEncoder(path, query string, headers http.Header) (*Encoder, error) { return NewEncoderWithRawPath(path, path, query, headers) } // NewHTTPBindingEncoder creates a new encoder from the passed in request. All query and // header values will be added on top of the request's existing values. Overwriting // duplicate values. func NewEncoderWithRawPath(path, rawPath, query string, headers http.Header) (*Encoder, error) { parseQuery, err := url.ParseQuery(query) if err != nil { return nil, fmt.Errorf("failed to parse query string: %w", err) } e := &Encoder{ path: []byte(path), rawPath: []byte(rawPath), query: parseQuery, header: headers.Clone(), } return e, nil } // Encode returns a REST protocol encoder for encoding HTTP bindings. // // Due net/http requiring `Content-Length` to be specified on the http.Request#ContentLength directly. Encode // will look for whether the header is present, and if so will remove it and set the respective value on http.Request. // // Returns any error occurring during encoding. func (e *Encoder) Encode(req *http.Request) (*http.Request, error) { req.URL.Path, req.URL.RawPath = string(e.path), string(e.rawPath) req.URL.RawQuery = e.query.Encode() // net/http ignores Content-Length header and requires it to be set on http.Request if v := e.header.Get(contentLengthHeader); len(v) > 0 { iv, err := strconv.ParseInt(v, 10, 64) if err != nil { return nil, err } req.ContentLength = iv e.header.Del(contentLengthHeader) } req.Header = e.header return req, nil } // AddHeader returns a HeaderValue for appending to the given header name func (e *Encoder) AddHeader(key string) HeaderValue { return newHeaderValue(e.header, key, true) } // SetHeader returns a HeaderValue for setting the given header name func (e *Encoder) SetHeader(key string) HeaderValue { return newHeaderValue(e.header, key, false) } // Headers returns a Header used for encoding headers with the given prefix func (e *Encoder) Headers(prefix string) Headers { return Headers{ header: e.header, prefix: strings.TrimSpace(prefix), } } // HasHeader returns if a header with the key specified exists with one or // more value. func (e Encoder) HasHeader(key string) bool { return len(e.header[key]) != 0 } // SetURI returns a URIValue used for setting the given path key func (e *Encoder) SetURI(key string) URIValue { return newURIValue(&e.path, &e.rawPath, &e.pathBuffer, key) } // SetQuery returns a QueryValue used for setting the given query key func (e *Encoder) SetQuery(key string) QueryValue { return NewQueryValue(e.query, key, false) } // AddQuery returns a QueryValue used for appending the given query key func (e *Encoder) AddQuery(key string) QueryValue { return NewQueryValue(e.query, key, true) } // HasQuery returns if a query with the key specified exists with one or // more values. func (e *Encoder) HasQuery(key string) bool { return len(e.query.Get(key)) != 0 } smithy-go-1.20.3/encoding/httpbinding/encode_test.go000066400000000000000000000064751463735525100224670ustar00rootroot00000000000000package httpbinding import ( "net/http" "net/url" "reflect" "testing" ) func TestEncoder(t *testing.T) { actual := &http.Request{ Header: http.Header{ "custom-user-header": {"someValue"}, }, URL: &url.URL{ Path: "/some/{pathKeyOne}/{pathKeyTwo}", RawQuery: "someExistingKeys=foobar", }, } expected := &http.Request{ Header: map[string][]string{ "custom-user-header": {"someValue"}, "x-amzn-header-foo": {"someValue"}, "x-amzn-meta-foo": {"someValue"}, }, URL: &url.URL{ Path: "/some/someValue/path", RawPath: "/some/someValue/path", RawQuery: "someExistingKeys=foobar&someKey=someValue&someKey=otherValue", }, } encoder, err := NewEncoder(actual.URL.Path, actual.URL.RawQuery, actual.Header) if err != nil { t.Fatalf("expected no error, got %v", err) } // Headers encoder.AddHeader("x-amzn-header-foo").String("someValue") encoder.Headers("x-amzn-meta-").AddHeader("foo").String("someValue") // Query encoder.SetQuery("someKey").String("someValue") encoder.AddQuery("someKey").String("otherValue") // URI if err := encoder.SetURI("pathKeyOne").String("someValue"); err != nil { t.Errorf("expected no err, but got %v", err) } // URI if err := encoder.SetURI("pathKeyTwo").String("path"); err != nil { t.Errorf("expected no err, but got %v", err) } if actual, err = encoder.Encode(actual); err != nil { t.Errorf("expected no err, but got %v", err) } if !reflect.DeepEqual(expected, actual) { t.Errorf("expected %v, but got %v", expected, actual) } } func TestEncoderHasHeader(t *testing.T) { encoder, err := NewEncoder("/", "", http.Header{}) if err != nil { t.Fatalf("expected no error, got %v", err) } if h := "i-dont-exist"; encoder.HasHeader(h) { t.Errorf("expect %v not to be set", h) } encoder.AddHeader("I-do-exist").String("some value") if h := "I-do-exist"; !encoder.HasHeader(h) { t.Errorf("expect %v to be set", h) } } func TestEncoderHasQuery(t *testing.T) { encoder, err := NewEncoder("/", "", http.Header{}) if err != nil { t.Fatalf("expected no error, got %v", err) } if q := "i-dont-exist"; encoder.HasQuery(q) { t.Errorf("expect %v not to be set", q) } encoder.AddQuery("I-do-exist").String("some value") if q := "I-do-exist"; !encoder.HasQuery(q) { t.Errorf("expect %v to be set", q) } } func TestEncodeContentLength(t *testing.T) { cases := map[string]struct { headerValue string expected int64 wantErr bool }{ "valid number": { headerValue: "1024", expected: 1024, }, "invalid number": { headerValue: "1024.5", wantErr: true, }, "not a number": { headerValue: "NaN", wantErr: true, }, } for name, tt := range cases { t.Run(name, func(t *testing.T) { encoder, err := NewEncoder("/", "", http.Header{}) if err != nil { t.Fatalf("expect no error, got %v", err) } encoder.SetHeader("Content-Length").String(tt.headerValue) req := &http.Request{URL: &url.URL{}} req, err = encoder.Encode(req) if (err != nil) != tt.wantErr { t.Fatalf("unexpected error value wantErr=%v", tt.wantErr) } else if tt.wantErr { return } if e, a := tt.expected, req.ContentLength; e != a { t.Errorf("expect %v, got %v", e, a) } if v := req.Header.Get("Content-Length"); len(v) > 0 { t.Errorf("expect header not to be set") } }) } } smithy-go-1.20.3/encoding/httpbinding/header.go000066400000000000000000000056511463735525100214160ustar00rootroot00000000000000package httpbinding import ( "encoding/base64" "math" "math/big" "net/http" "strconv" "strings" ) // Headers is used to encode header keys using a provided prefix type Headers struct { header http.Header prefix string } // AddHeader returns a HeaderValue used to append values to prefix+key func (h Headers) AddHeader(key string) HeaderValue { return h.newHeaderValue(key, true) } // SetHeader returns a HeaderValue used to set the value of prefix+key func (h Headers) SetHeader(key string) HeaderValue { return h.newHeaderValue(key, false) } func (h Headers) newHeaderValue(key string, append bool) HeaderValue { return newHeaderValue(h.header, h.prefix+strings.TrimSpace(key), append) } // HeaderValue is used to encode values to an HTTP header type HeaderValue struct { header http.Header key string append bool } func newHeaderValue(header http.Header, key string, append bool) HeaderValue { return HeaderValue{header: header, key: strings.TrimSpace(key), append: append} } func (h HeaderValue) modifyHeader(value string) { if h.append { h.header[h.key] = append(h.header[h.key], value) } else { h.header[h.key] = append(h.header[h.key][:0], value) } } // String encodes the value v as the header string value func (h HeaderValue) String(v string) { h.modifyHeader(v) } // Byte encodes the value v as a query string value func (h HeaderValue) Byte(v int8) { h.Long(int64(v)) } // Short encodes the value v as a query string value func (h HeaderValue) Short(v int16) { h.Long(int64(v)) } // Integer encodes the value v as the header string value func (h HeaderValue) Integer(v int32) { h.Long(int64(v)) } // Long encodes the value v as the header string value func (h HeaderValue) Long(v int64) { h.modifyHeader(strconv.FormatInt(v, 10)) } // Boolean encodes the value v as a query string value func (h HeaderValue) Boolean(v bool) { h.modifyHeader(strconv.FormatBool(v)) } // Float encodes the value v as a query string value func (h HeaderValue) Float(v float32) { h.float(float64(v), 32) } // Double encodes the value v as a query string value func (h HeaderValue) Double(v float64) { h.float(v, 64) } func (h HeaderValue) float(v float64, bitSize int) { switch { case math.IsNaN(v): h.String(floatNaN) case math.IsInf(v, 1): h.String(floatInfinity) case math.IsInf(v, -1): h.String(floatNegInfinity) default: h.modifyHeader(strconv.FormatFloat(v, 'f', -1, bitSize)) } } // BigInteger encodes the value v as a query string value func (h HeaderValue) BigInteger(v *big.Int) { h.modifyHeader(v.String()) } // BigDecimal encodes the value v as a query string value func (h HeaderValue) BigDecimal(v *big.Float) { if i, accuracy := v.Int64(); accuracy == big.Exact { h.Long(i) return } h.modifyHeader(v.Text('e', -1)) } // Blob encodes the value v as a base64 header string value func (h HeaderValue) Blob(v []byte) { encodeToString := base64.StdEncoding.EncodeToString(v) h.modifyHeader(encodeToString) } smithy-go-1.20.3/encoding/httpbinding/header_test.go000066400000000000000000000167711463735525100224620ustar00rootroot00000000000000package httpbinding import ( "fmt" "math/big" "net/http" "reflect" "testing" ) func TestHeaderValue(t *testing.T) { const keyName = "test-key" const expectedKeyName = "test-key" cases := map[string]struct { header http.Header args []interface{} append bool expected http.Header }{ "set blob": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{[]byte("baz")}, expected: map[string][]string{ expectedKeyName: {"YmF6"}, }, }, "set boolean": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{true}, expected: map[string][]string{ expectedKeyName: {"true"}, }, }, "set string": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{"string value"}, expected: map[string][]string{ expectedKeyName: {"string value"}, }, }, "set byte": { header: http.Header{expectedKeyName: []string{"127"}}, args: []interface{}{int8(127)}, expected: map[string][]string{ expectedKeyName: {"127"}, }, }, "set short": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{int16(32767)}, expected: map[string][]string{ expectedKeyName: {"32767"}, }, }, "set integer": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{int32(2147483647)}, expected: map[string][]string{ expectedKeyName: {"2147483647"}, }, }, "set long": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{int64(9223372036854775807)}, expected: map[string][]string{ expectedKeyName: {"9223372036854775807"}, }, }, "set float": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{float32(3.14159)}, expected: map[string][]string{ expectedKeyName: {"3.14159"}, }, }, "set double": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{float64(3.14159)}, expected: map[string][]string{ expectedKeyName: {"3.14159"}, }, }, "set bigInteger": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{new(big.Int).SetInt64(42)}, expected: map[string][]string{ expectedKeyName: {"42"}, }, }, "set bigDecimal": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{new(big.Float).SetFloat64(1024.10241024)}, expected: map[string][]string{ expectedKeyName: {"1.02410241024e+03"}, }, }, "add blob": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{[]byte("baz")}, append: true, expected: map[string][]string{ expectedKeyName: {"foobar", "YmF6"}, }, }, "add bool": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{true}, append: true, expected: map[string][]string{ expectedKeyName: {"foobar", "true"}, }, }, "add string": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{"string value"}, append: true, expected: map[string][]string{ expectedKeyName: {"foobar", "string value"}, }, }, "add byte": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{int8(127)}, append: true, expected: map[string][]string{ expectedKeyName: {"foobar", "127"}, }, }, "add short": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{int16(32767)}, append: true, expected: map[string][]string{ expectedKeyName: {"foobar", "32767"}, }, }, "add integer": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{int32(2147483647)}, append: true, expected: map[string][]string{ expectedKeyName: {"foobar", "2147483647"}, }, }, "add long": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{int64(9223372036854775807)}, append: true, expected: map[string][]string{ expectedKeyName: {"foobar", "9223372036854775807"}, }, }, "add float": { header: http.Header{expectedKeyName: []string{"1.61803"}}, args: []interface{}{float32(3.14159)}, append: true, expected: map[string][]string{ expectedKeyName: {"1.61803", "3.14159"}, }, }, "add double": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{float64(3.14159)}, append: true, expected: map[string][]string{ expectedKeyName: {"foobar", "3.14159"}, }, }, "add bigInteger": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{new(big.Int).SetInt64(42)}, append: true, expected: map[string][]string{ expectedKeyName: {"foobar", "42"}, }, }, "add bigDecimal": { header: http.Header{expectedKeyName: []string{"foobar"}}, args: []interface{}{new(big.Float).SetFloat64(1024.10241024)}, append: true, expected: map[string][]string{ expectedKeyName: {"foobar", "1.02410241024e+03"}, }, }, } for name, tt := range cases { t.Run(name, func(t *testing.T) { if tt.header == nil { tt.header = http.Header{} } hv := newHeaderValue(tt.header, keyName, tt.append) if err := setHeader(hv, tt.args); err != nil { t.Fatalf("expected no error, got %v", err) } if e, a := tt.expected, hv.header; !reflect.DeepEqual(e, a) { t.Errorf("expected %v, got %v", e, a) } }) } } func TestHeaders(t *testing.T) { const prefix = "X-Amzn-Meta-" cases := map[string]struct { headers http.Header values map[string]string append bool expected http.Header }{ "set": { headers: http.Header{ "X-Amzn-Meta-Foo": {"bazValue"}, }, values: map[string]string{ "Foo": "fooValue", " Bar ": "barValue", }, expected: http.Header{ "X-Amzn-Meta-Foo": {"fooValue"}, "X-Amzn-Meta-Bar": {"barValue"}, }, }, "add": { headers: http.Header{ "X-Amzn-Meta-Foo": {"bazValue"}, }, values: map[string]string{ "Foo": "fooValue", " Bar ": "barValue", }, append: true, expected: http.Header{ "X-Amzn-Meta-Foo": {"bazValue", "fooValue"}, "X-Amzn-Meta-Bar": {"barValue"}, }, }, } for name, tt := range cases { t.Run(name, func(t *testing.T) { headers := Headers{header: tt.headers, prefix: prefix} var f func(key string) HeaderValue if tt.append { f = headers.AddHeader } else { f = headers.SetHeader } for key, value := range tt.values { f(key).String(value) } if e, a := tt.expected, tt.headers; !reflect.DeepEqual(e, a) { t.Errorf("expected %v, but got %v", e, a) } }) } } func setHeader(hv HeaderValue, args []interface{}) error { value := args[0] switch value.(type) { case []byte: return reflectCall(reflect.ValueOf(hv.Blob), args) case bool: return reflectCall(reflect.ValueOf(hv.Boolean), args) case string: return reflectCall(reflect.ValueOf(hv.String), args) case int8: return reflectCall(reflect.ValueOf(hv.Byte), args) case int16: return reflectCall(reflect.ValueOf(hv.Short), args) case int32: return reflectCall(reflect.ValueOf(hv.Integer), args) case int64: return reflectCall(reflect.ValueOf(hv.Long), args) case float32: return reflectCall(reflect.ValueOf(hv.Float), args) case float64: return reflectCall(reflect.ValueOf(hv.Double), args) case *big.Int: return reflectCall(reflect.ValueOf(hv.BigInteger), args) case *big.Float: return reflectCall(reflect.ValueOf(hv.BigDecimal), args) default: return fmt.Errorf("unhandled header value type") } } smithy-go-1.20.3/encoding/httpbinding/path_replace.go000066400000000000000000000044711463735525100226140ustar00rootroot00000000000000package httpbinding import ( "bytes" "fmt" ) const ( uriTokenStart = '{' uriTokenStop = '}' uriTokenSkip = '+' ) func bufCap(b []byte, n int) []byte { if cap(b) < n { return make([]byte, 0, n) } return b[0:0] } // replacePathElement replaces a single element in the path []byte. // Escape is used to control whether the value will be escaped using Amazon path escape style. func replacePathElement(path, fieldBuf []byte, key, val string, escape bool) ([]byte, []byte, error) { fieldBuf = bufCap(fieldBuf, len(key)+3) // { [+] } fieldBuf = append(fieldBuf, uriTokenStart) fieldBuf = append(fieldBuf, key...) start := bytes.Index(path, fieldBuf) end := start + len(fieldBuf) if start < 0 || len(path[end:]) == 0 { // TODO what to do about error? return path, fieldBuf, fmt.Errorf("invalid path index, start=%d,end=%d. %s", start, end, path) } encodeSep := true if path[end] == uriTokenSkip { // '+' token means do not escape slashes encodeSep = false end++ } if escape { val = EscapePath(val, encodeSep) } if path[end] != uriTokenStop { return path, fieldBuf, fmt.Errorf("invalid path element, does not contain token stop, %s", path) } end++ fieldBuf = bufCap(fieldBuf, len(val)) fieldBuf = append(fieldBuf, val...) keyLen := end - start valLen := len(fieldBuf) if keyLen == valLen { copy(path[start:], fieldBuf) return path, fieldBuf, nil } newLen := len(path) + (valLen - keyLen) if len(path) < newLen { path = path[:cap(path)] } if cap(path) < newLen { newURI := make([]byte, newLen) copy(newURI, path) path = newURI } // shift copy(path[start+valLen:], path[end:]) path = path[:newLen] copy(path[start:], fieldBuf) return path, fieldBuf, nil } // EscapePath escapes part of a URL path in Amazon style. func EscapePath(path string, encodeSep bool) string { var buf bytes.Buffer for i := 0; i < len(path); i++ { c := path[i] if noEscape[c] || (c == '/' && !encodeSep) { buf.WriteByte(c) } else { fmt.Fprintf(&buf, "%%%02X", c) } } return buf.String() } var noEscape [256]bool func init() { for i := 0; i < len(noEscape); i++ { // AWS expects every character except these to be escaped noEscape[i] = (i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || (i >= '0' && i <= '9') || i == '-' || i == '.' || i == '_' || i == '~' } } smithy-go-1.20.3/encoding/httpbinding/path_replace_test.go000066400000000000000000000034021463735525100236440ustar00rootroot00000000000000package httpbinding import ( "bytes" "testing" ) func TestPathReplace(t *testing.T) { cases := []struct { Orig, ExpPath, ExpRawPath []byte Key, Val string }{ { Orig: []byte("/{bucket}/{key+}"), ExpPath: []byte("/123/{key+}"), ExpRawPath: []byte("/123/{key+}"), Key: "bucket", Val: "123", }, { Orig: []byte("/{bucket}/{key+}"), ExpPath: []byte("/{bucket}/abc"), ExpRawPath: []byte("/{bucket}/abc"), Key: "key", Val: "abc", }, { Orig: []byte("/{bucket}/{key+}"), ExpPath: []byte("/{bucket}/a/b/c"), ExpRawPath: []byte("/{bucket}/a/b/c"), Key: "key", Val: "a/b/c", }, { Orig: []byte("/{bucket}/{key+}"), ExpPath: []byte("/1/2/3/{key+}"), ExpRawPath: []byte("/1%2F2%2F3/{key+}"), Key: "bucket", Val: "1/2/3", }, { Orig: []byte("/{bucket}/{key+}"), ExpPath: []byte("/reallylongvaluegoesheregrowingarray/{key+}"), ExpRawPath: []byte("/reallylongvaluegoesheregrowingarray/{key+}"), Key: "bucket", Val: "reallylongvaluegoesheregrowingarray", }, } var buffer [64]byte for i, c := range cases { origRaw := make([]byte, len(c.Orig)) copy(origRaw, c.Orig) path, _, err := replacePathElement(c.Orig, buffer[:0], c.Key, c.Val, false) if err != nil { t.Fatalf("expected no error, got %v", err) } rawPath, _, err := replacePathElement(origRaw, buffer[:0], c.Key, c.Val, true) if err != nil { t.Fatalf("expected no error, got %v", err) } if e, a := c.ExpPath, path; bytes.Compare(e, a) != 0 { t.Errorf("%d, expect uri path to be %q got %q", i, e, a) } if e, a := c.ExpRawPath, rawPath; bytes.Compare(e, a) != 0 { t.Errorf("%d, expect uri raw path to be %q got %q", i, e, a) } } } smithy-go-1.20.3/encoding/httpbinding/query.go000066400000000000000000000043561463735525100213340ustar00rootroot00000000000000package httpbinding import ( "encoding/base64" "math" "math/big" "net/url" "strconv" ) // QueryValue is used to encode query key values type QueryValue struct { query url.Values key string append bool } // NewQueryValue creates a new QueryValue which enables encoding // a query value into the given url.Values. func NewQueryValue(query url.Values, key string, append bool) QueryValue { return QueryValue{ query: query, key: key, append: append, } } func (qv QueryValue) updateKey(value string) { if qv.append { qv.query.Add(qv.key, value) } else { qv.query.Set(qv.key, value) } } // Blob encodes v as a base64 query string value func (qv QueryValue) Blob(v []byte) { encodeToString := base64.StdEncoding.EncodeToString(v) qv.updateKey(encodeToString) } // Boolean encodes v as a query string value func (qv QueryValue) Boolean(v bool) { qv.updateKey(strconv.FormatBool(v)) } // String encodes v as a query string value func (qv QueryValue) String(v string) { qv.updateKey(v) } // Byte encodes v as a query string value func (qv QueryValue) Byte(v int8) { qv.Long(int64(v)) } // Short encodes v as a query string value func (qv QueryValue) Short(v int16) { qv.Long(int64(v)) } // Integer encodes v as a query string value func (qv QueryValue) Integer(v int32) { qv.Long(int64(v)) } // Long encodes v as a query string value func (qv QueryValue) Long(v int64) { qv.updateKey(strconv.FormatInt(v, 10)) } // Float encodes v as a query string value func (qv QueryValue) Float(v float32) { qv.float(float64(v), 32) } // Double encodes v as a query string value func (qv QueryValue) Double(v float64) { qv.float(v, 64) } func (qv QueryValue) float(v float64, bitSize int) { switch { case math.IsNaN(v): qv.String(floatNaN) case math.IsInf(v, 1): qv.String(floatInfinity) case math.IsInf(v, -1): qv.String(floatNegInfinity) default: qv.updateKey(strconv.FormatFloat(v, 'f', -1, bitSize)) } } // BigInteger encodes v as a query string value func (qv QueryValue) BigInteger(v *big.Int) { qv.updateKey(v.String()) } // BigDecimal encodes v as a query string value func (qv QueryValue) BigDecimal(v *big.Float) { if i, accuracy := v.Int64(); accuracy == big.Exact { qv.Long(i) return } qv.updateKey(v.Text('e', -1)) } smithy-go-1.20.3/encoding/httpbinding/query_test.go000066400000000000000000000136431463735525100223720ustar00rootroot00000000000000package httpbinding import ( "fmt" "math/big" "net/url" "reflect" "testing" ) func TestQueryValue(t *testing.T) { const queryKey = "someKey" cases := map[string]struct { values url.Values args []interface{} append bool expected url.Values }{ "set blob": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{[]byte("baz")}, expected: map[string][]string{ queryKey: {"YmF6"}, }, }, "set bool": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{true}, expected: map[string][]string{ queryKey: {"true"}, }, }, "set string": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{"string value"}, expected: map[string][]string{ queryKey: {"string value"}, }, }, "set byte": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{int8(127)}, expected: map[string][]string{ queryKey: {"127"}, }, }, "set short": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{int16(32767)}, expected: map[string][]string{ queryKey: {"32767"}, }, }, "set integer": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{int32(2147483647)}, expected: map[string][]string{ queryKey: {"2147483647"}, }, }, "set long": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{int64(9223372036854775807)}, expected: map[string][]string{ queryKey: {"9223372036854775807"}, }, }, "set float": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{float32(3.14159)}, expected: map[string][]string{ queryKey: {"3.14159"}, }, }, "set double": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{float64(3.14159)}, expected: map[string][]string{ queryKey: {"3.14159"}, }, }, "set bigInteger": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{new(big.Int).SetInt64(1)}, expected: map[string][]string{ queryKey: {"1"}, }, }, "set bigDecimal": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{new(big.Float).SetFloat64(1024.10241024)}, expected: map[string][]string{ queryKey: {"1.02410241024e+03"}, }, }, "add blob": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{[]byte("baz")}, append: true, expected: map[string][]string{ queryKey: {"foobar", "YmF6"}, }, }, "add bool": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{true}, append: true, expected: map[string][]string{ queryKey: {"foobar", "true"}, }, }, "add string": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{"string value"}, append: true, expected: map[string][]string{ queryKey: {"foobar", "string value"}, }, }, "add byte": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{int8(127)}, append: true, expected: map[string][]string{ queryKey: {"foobar", "127"}, }, }, "add short": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{int16(32767)}, append: true, expected: map[string][]string{ queryKey: {"foobar", "32767"}, }, }, "add integer": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{int32(2147483647)}, expected: map[string][]string{ queryKey: {"2147483647"}, }, }, "add long": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{int64(9223372036854775807)}, append: true, expected: map[string][]string{ queryKey: {"foobar", "9223372036854775807"}, }, }, "add float": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{float32(3.14159)}, append: true, expected: map[string][]string{ queryKey: {"foobar", "3.14159"}, }, }, "add double": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{float64(3.14159)}, append: true, expected: map[string][]string{ queryKey: {"foobar", "3.14159"}, }, }, "add bigInteger": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{new(big.Int).SetInt64(1)}, append: true, expected: map[string][]string{ queryKey: {"foobar", "1"}, }, }, "add bigDecimal": { values: url.Values{queryKey: []string{"foobar"}}, args: []interface{}{new(big.Float).SetFloat64(1024.10241024)}, append: true, expected: map[string][]string{ queryKey: {"foobar", "1.02410241024e+03"}, }, }, } for name, tt := range cases { t.Run(name, func(t *testing.T) { if tt.values == nil { tt.values = url.Values{} } qv := NewQueryValue(tt.values, queryKey, tt.append) if err := setQueryValue(qv, tt.args); err != nil { t.Fatalf("expected no error, got %v", err) } if e, a := tt.expected, qv.query; !reflect.DeepEqual(e, a) { t.Errorf("expected %v, got %v", e, a) } }) } } func setQueryValue(qv QueryValue, args []interface{}) error { value := args[0] switch value.(type) { case []byte: return reflectCall(reflect.ValueOf(qv.Blob), args) case bool: return reflectCall(reflect.ValueOf(qv.Boolean), args) case string: return reflectCall(reflect.ValueOf(qv.String), args) case int8: return reflectCall(reflect.ValueOf(qv.Byte), args) case int16: return reflectCall(reflect.ValueOf(qv.Short), args) case int32: return reflectCall(reflect.ValueOf(qv.Integer), args) case int64: return reflectCall(reflect.ValueOf(qv.Long), args) case float32: return reflectCall(reflect.ValueOf(qv.Float), args) case float64: return reflectCall(reflect.ValueOf(qv.Double), args) case *big.Int: return reflectCall(reflect.ValueOf(qv.BigInteger), args) case *big.Float: return reflectCall(reflect.ValueOf(qv.BigDecimal), args) default: return fmt.Errorf("unhandled query value type") } } smithy-go-1.20.3/encoding/httpbinding/shared_test.go000066400000000000000000000013671463735525100224730ustar00rootroot00000000000000package httpbinding import ( "fmt" "reflect" ) func reflectCall(funcValue reflect.Value, args []interface{}) error { argValues := make([]reflect.Value, len(args)) for i, v := range args { value := reflect.ValueOf(v) argValues[i] = value } retValues := funcValue.Call(argValues) if len(retValues) > 0 { errValue := retValues[0] if typeName := errValue.Type().Name(); typeName != "error" { panic(fmt.Sprintf("expected first return argument to be error but got %v", typeName)) } if errValue.IsNil() { return nil } if err, ok := errValue.Interface().(error); ok { return err } panic(fmt.Sprintf("expected %v to return error type, but got %v", funcValue.Type().String(), retValues[0].Type().String())) } return nil } smithy-go-1.20.3/encoding/httpbinding/uri.go000066400000000000000000000050151463735525100207570ustar00rootroot00000000000000package httpbinding import ( "math" "math/big" "strconv" "strings" ) // URIValue is used to encode named URI parameters type URIValue struct { path, rawPath, buffer *[]byte key string } func newURIValue(path *[]byte, rawPath *[]byte, buffer *[]byte, key string) URIValue { return URIValue{path: path, rawPath: rawPath, buffer: buffer, key: key} } func (u URIValue) modifyURI(value string) (err error) { *u.path, *u.buffer, err = replacePathElement(*u.path, *u.buffer, u.key, value, false) if err != nil { return err } *u.rawPath, *u.buffer, err = replacePathElement(*u.rawPath, *u.buffer, u.key, value, true) return err } // Boolean encodes v as a URI string value func (u URIValue) Boolean(v bool) error { return u.modifyURI(strconv.FormatBool(v)) } // String encodes v as a URI string value func (u URIValue) String(v string) error { return u.modifyURI(v) } // Byte encodes v as a URI string value func (u URIValue) Byte(v int8) error { return u.Long(int64(v)) } // Short encodes v as a URI string value func (u URIValue) Short(v int16) error { return u.Long(int64(v)) } // Integer encodes v as a URI string value func (u URIValue) Integer(v int32) error { return u.Long(int64(v)) } // Long encodes v as a URI string value func (u URIValue) Long(v int64) error { return u.modifyURI(strconv.FormatInt(v, 10)) } // Float encodes v as a query string value func (u URIValue) Float(v float32) error { return u.float(float64(v), 32) } // Double encodes v as a query string value func (u URIValue) Double(v float64) error { return u.float(v, 64) } func (u URIValue) float(v float64, bitSize int) error { switch { case math.IsNaN(v): return u.String(floatNaN) case math.IsInf(v, 1): return u.String(floatInfinity) case math.IsInf(v, -1): return u.String(floatNegInfinity) default: return u.modifyURI(strconv.FormatFloat(v, 'f', -1, bitSize)) } } // BigInteger encodes v as a query string value func (u URIValue) BigInteger(v *big.Int) error { return u.modifyURI(v.String()) } // BigDecimal encodes v as a query string value func (u URIValue) BigDecimal(v *big.Float) error { if i, accuracy := v.Int64(); accuracy == big.Exact { return u.Long(i) } return u.modifyURI(v.Text('e', -1)) } // SplitURI parses a Smithy HTTP binding trait URI func SplitURI(uri string) (path, query string) { queryStart := strings.IndexRune(uri, '?') if queryStart == -1 { path = uri return path, query } path = uri[:queryStart] if queryStart+1 >= len(uri) { return path, query } query = uri[queryStart+1:] return path, query } smithy-go-1.20.3/encoding/httpbinding/uri_test.go000066400000000000000000000101501463735525100220120ustar00rootroot00000000000000package httpbinding import ( "fmt" "math/big" "reflect" "strconv" "testing" ) func TestURIValue(t *testing.T) { const uriKey = "someKey" const path = "/some/{someKey}/{path+}" type expected struct { path string raw string } cases := map[string]struct { path string args []interface{} expected expected }{ "bool": { path: path, args: []interface{}{true}, expected: expected{ path: "/some/true/{path+}", raw: "/some/true/{path+}", }, }, "string": { path: path, args: []interface{}{"someValue"}, expected: expected{ path: "/some/someValue/{path+}", raw: "/some/someValue/{path+}", }, }, "byte": { path: path, args: []interface{}{int8(127)}, expected: expected{ path: "/some/127/{path+}", raw: "/some/127/{path+}", }, }, "short": { path: path, args: []interface{}{int16(32767)}, expected: expected{ path: "/some/32767/{path+}", raw: "/some/32767/{path+}", }, }, "integer": { path: path, args: []interface{}{int32(2147483647)}, expected: expected{ path: "/some/2147483647/{path+}", raw: "/some/2147483647/{path+}", }, }, "long": { path: path, args: []interface{}{int64(9223372036854775807)}, expected: expected{ path: "/some/9223372036854775807/{path+}", raw: "/some/9223372036854775807/{path+}", }, }, "float32": { path: path, args: []interface{}{float32(3.14159)}, expected: expected{ path: "/some/3.14159/{path+}", raw: "/some/3.14159/{path+}", }, }, "float64": { path: path, args: []interface{}{float64(3.14159)}, expected: expected{ path: "/some/3.14159/{path+}", raw: "/some/3.14159/{path+}", }, }, "bigInteger": { path: path, args: []interface{}{new(big.Int).SetInt64(1)}, expected: expected{ path: "/some/1/{path+}", raw: "/some/1/{path+}", }, }, "bigDecimal": { path: path, args: []interface{}{new(big.Float).SetFloat64(1024.10241024)}, expected: expected{ path: "/some/1.02410241024e+03/{path+}", raw: "/some/1.02410241024e%2B03/{path+}", }, }, } buffer := make([]byte, 1024) for name, tt := range cases { t.Run(name, func(t *testing.T) { pBytes, rBytes := []byte(tt.path), []byte(tt.path) uv := newURIValue(&pBytes, &rBytes, &buffer, uriKey) if err := setURI(uv, tt.args); err != nil { t.Fatalf("expected no error, %v", err) } if e, a := tt.expected.path, string(pBytes); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := tt.expected.raw, string(rBytes); e != a { t.Errorf("expected %v, got %v", e, a) } }) } } func setURI(uv URIValue, args []interface{}) error { value := args[0] switch value.(type) { case bool: return reflectCall(reflect.ValueOf(uv.Boolean), args) case string: return reflectCall(reflect.ValueOf(uv.String), args) case int8: return reflectCall(reflect.ValueOf(uv.Byte), args) case int16: return reflectCall(reflect.ValueOf(uv.Short), args) case int32: return reflectCall(reflect.ValueOf(uv.Integer), args) case int64: return reflectCall(reflect.ValueOf(uv.Long), args) case float32: return reflectCall(reflect.ValueOf(uv.Float), args) case float64: return reflectCall(reflect.ValueOf(uv.Double), args) case *big.Int: return reflectCall(reflect.ValueOf(uv.BigInteger), args) case *big.Float: return reflectCall(reflect.ValueOf(uv.BigDecimal), args) default: return fmt.Errorf("unhandled value type") } } func TestParseURI(t *testing.T) { cases := []struct { Value string Path string Query string }{ { Value: "/my/uri/foo/bar/baz", Path: "/my/uri/foo/bar/baz", Query: "", }, { Value: "/path?requiredKey", Path: "/path", Query: "requiredKey", }, { Value: "/path?", Path: "/path", Query: "", }, { Value: "?", Path: "", Query: "", }, } for i, tt := range cases { t.Run(strconv.Itoa(i), func(t *testing.T) { path, query := SplitURI(tt.Value) if e, a := tt.Path, path; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := tt.Query, query; e != a { t.Errorf("expected %v, got %v", e, a) } }) } } smithy-go-1.20.3/encoding/json/000077500000000000000000000000001463735525100162675ustar00rootroot00000000000000smithy-go-1.20.3/encoding/json/array.go000066400000000000000000000012211463735525100177300ustar00rootroot00000000000000package json import ( "bytes" ) // Array represents the encoding of a JSON Array type Array struct { w *bytes.Buffer writeComma bool scratch *[]byte } func newArray(w *bytes.Buffer, scratch *[]byte) *Array { w.WriteRune(leftBracket) return &Array{w: w, scratch: scratch} } // Value adds a new element to the JSON Array. // Returns a Value type that is used to encode // the array element. func (a *Array) Value() Value { if a.writeComma { a.w.WriteRune(comma) } else { a.writeComma = true } return newValue(a.w, a.scratch) } // Close encodes the end of the JSON Array func (a *Array) Close() { a.w.WriteRune(rightBracket) } smithy-go-1.20.3/encoding/json/array_test.go000066400000000000000000000005771463735525100210040ustar00rootroot00000000000000package json import ( "bytes" "testing" ) func TestArray(t *testing.T) { buffer := bytes.NewBuffer(nil) scratch := make([]byte, 64) array := newArray(buffer, &scratch) array.Value().String("bar") array.Value().String("baz") array.Close() e := []byte(`["bar","baz"]`) if a := buffer.Bytes(); bytes.Compare(e, a) != 0 { t.Errorf("expected %+q, but got %+q", e, a) } } smithy-go-1.20.3/encoding/json/constants.go000066400000000000000000000002351463735525100206320ustar00rootroot00000000000000package json const ( leftBrace = '{' rightBrace = '}' leftBracket = '[' rightBracket = ']' comma = ',' quote = '"' colon = ':' null = "null" ) smithy-go-1.20.3/encoding/json/decoder_util.go000066400000000000000000000063561463735525100212720ustar00rootroot00000000000000package json import ( "bytes" "encoding/json" "fmt" "io" ) // DiscardUnknownField discards unknown fields from a decoder body. // This function is useful while deserializing a JSON body with additional // unknown information that should be discarded. func DiscardUnknownField(decoder *json.Decoder) error { // This deliberately does not share logic with CollectUnknownField, even // though it could, because if we were to delegate to that then we'd incur // extra allocations and general memory usage. v, err := decoder.Token() if err == io.EOF { return nil } if err != nil { return err } if _, ok := v.(json.Delim); ok { for decoder.More() { err = DiscardUnknownField(decoder) } endToken, err := decoder.Token() if err != nil { return err } if _, ok := endToken.(json.Delim); !ok { return fmt.Errorf("invalid JSON : expected json delimiter, found %T %v", endToken, endToken) } } return nil } // CollectUnknownField grabs the contents of unknown fields from the decoder body // and returns them as a byte slice. This is useful for skipping unknown fields without // completely discarding them. func CollectUnknownField(decoder *json.Decoder) ([]byte, error) { result, err := collectUnknownField(decoder) if err != nil { return nil, err } buff := bytes.NewBuffer(nil) encoder := json.NewEncoder(buff) if err := encoder.Encode(result); err != nil { return nil, err } return buff.Bytes(), nil } func collectUnknownField(decoder *json.Decoder) (interface{}, error) { // Grab the initial value. This could either be a concrete value like a string or a a // delimiter. token, err := decoder.Token() if err == io.EOF { return nil, nil } if err != nil { return nil, err } // If it's an array or object, we'll need to recurse. delim, ok := token.(json.Delim) if ok { var result interface{} if delim == '{' { result, err = collectUnknownObject(decoder) if err != nil { return nil, err } } else { result, err = collectUnknownArray(decoder) if err != nil { return nil, err } } // Discard the closing token. decoder.Token handles checking for matching delimiters if _, err := decoder.Token(); err != nil { return nil, err } return result, nil } return token, nil } func collectUnknownArray(decoder *json.Decoder) ([]interface{}, error) { // We need to create an empty array here instead of a nil array, since by getting // into this function at all we necessarily have seen a non-nil list. array := []interface{}{} for decoder.More() { value, err := collectUnknownField(decoder) if err != nil { return nil, err } array = append(array, value) } return array, nil } func collectUnknownObject(decoder *json.Decoder) (map[string]interface{}, error) { object := make(map[string]interface{}) for decoder.More() { key, err := collectUnknownField(decoder) if err != nil { return nil, err } // Keys have to be strings, which is particularly important as the encoder // won't except a map with interface{} keys stringKey, ok := key.(string) if !ok { return nil, fmt.Errorf("expected string key, found %T", key) } value, err := collectUnknownField(decoder) if err != nil { return nil, err } object[stringKey] = value } return object, nil } smithy-go-1.20.3/encoding/json/decoder_util_test.go000066400000000000000000000032701463735525100223210ustar00rootroot00000000000000package json import ( "bytes" "encoding/json" "testing" smithytesting "github.com/aws/smithy-go/testing" ) func TestDiscardUnknownField(t *testing.T) { cases := map[string][]byte{ "empty object": []byte(`{}`), "simple object": []byte(`{"foo": "bar"}`), "nested object": []byte(`{"foo": {"bar": "baz"}}`), "empty list": []byte(`[]`), "simple list": []byte(`["foo", "bar", "baz"]`), "nested list": []byte(`["foo", ["bar", ["baz"]]]`), "number": []byte(`1`), "boolean": []byte(`true`), "null": []byte(`null`), "string": []byte(`"foo"`), } for name, c := range cases { t.Run(name, func(t *testing.T) { buff := bytes.NewBuffer(c) decoder := json.NewDecoder(buff) err := DiscardUnknownField(decoder) if err != nil { t.Fatalf("failed to discard, %v", err) } if decoder.More() { t.Fatalf("failed to discard entire contents") } }) } } func TestCollectUnknownField(t *testing.T) { cases := map[string][]byte{ "empty object": []byte(`{}`), "simple object": []byte(`{"foo": "bar"}`), "nested object": []byte(`{"foo": {"bar": "baz"}}`), "empty list": []byte(`[]`), "simple list": []byte(`["foo", "bar", "baz"]`), "nested list": []byte(`["foo", ["bar", ["baz"]]]`), "number": []byte(`1`), "boolean": []byte(`true`), "null": []byte(`null`), "string": []byte(`"foo"`), } for name, c := range cases { t.Run(name, func(t *testing.T) { buff := bytes.NewBuffer(c) decoder := json.NewDecoder(buff) actual, err := CollectUnknownField(decoder) if err != nil { t.Fatalf("failed to collect, %v", err) } smithytesting.AssertJSONEqual(t, c, actual) }) } } smithy-go-1.20.3/encoding/json/encoder.go000066400000000000000000000011221463735525100202310ustar00rootroot00000000000000package json import ( "bytes" ) // Encoder is JSON encoder that supports construction of JSON values // using methods. type Encoder struct { w *bytes.Buffer Value } // NewEncoder returns a new JSON encoder func NewEncoder() *Encoder { writer := bytes.NewBuffer(nil) scratch := make([]byte, 64) return &Encoder{w: writer, Value: newValue(writer, &scratch)} } // String returns the String output of the JSON encoder func (e Encoder) String() string { return e.w.String() } // Bytes returns the []byte slice of the JSON encoder func (e Encoder) Bytes() []byte { return e.w.Bytes() } smithy-go-1.20.3/encoding/json/encoder_test.go000066400000000000000000000013611463735525100212750ustar00rootroot00000000000000package json_test import ( "bytes" "testing" "github.com/aws/smithy-go/encoding/json" ) func TestEncoder(t *testing.T) { encoder := json.NewEncoder() object := encoder.Object() object.Key("stringKey").String("stringValue") object.Key("integerKey").Long(1024) object.Key("floatKey").Double(3.14) subObj := object.Key("foo").Object() subObj.Key("byteSlice").Base64EncodeBytes([]byte("foo bar")) subObj.Close() object.Close() e := []byte(`{"stringKey":"stringValue","integerKey":1024,"floatKey":3.14,"foo":{"byteSlice":"Zm9vIGJhcg=="}}`) if a := encoder.Bytes(); bytes.Compare(e, a) != 0 { t.Errorf("expected %+q, but got %+q", e, a) } if a := encoder.String(); string(e) != a { t.Errorf("expected %s, but got %s", e, a) } } smithy-go-1.20.3/encoding/json/escape.go000066400000000000000000000101721463735525100200570ustar00rootroot00000000000000// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Copied and modified from Go 1.8 stdlib's encoding/json/#safeSet package json import ( "bytes" "unicode/utf8" ) // safeSet holds the value true if the ASCII character with the given array // position can be represented inside a JSON string without any further // escaping. // // All values are true except for the ASCII control characters (0-31), the // double quote ("), and the backslash character ("\"). var safeSet = [utf8.RuneSelf]bool{ ' ': true, '!': true, '"': false, '#': true, '$': true, '%': true, '&': true, '\'': true, '(': true, ')': true, '*': true, '+': true, ',': true, '-': true, '.': true, '/': true, '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true, ':': true, ';': true, '<': true, '=': true, '>': true, '?': true, '@': true, 'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true, 'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true, 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true, 'Y': true, 'Z': true, '[': true, '\\': false, ']': true, '^': true, '_': true, '`': true, 'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true, 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true, 'y': true, 'z': true, '{': true, '|': true, '}': true, '~': true, '\u007f': true, } // copied from Go 1.8 stdlib's encoding/json/#hex var hex = "0123456789abcdef" // escapeStringBytes escapes and writes the passed in string bytes to the dst // buffer // // Copied and modifed from Go 1.8 stdlib's encodeing/json/#encodeState.stringBytes func escapeStringBytes(e *bytes.Buffer, s []byte) { e.WriteByte('"') start := 0 for i := 0; i < len(s); { if b := s[i]; b < utf8.RuneSelf { if safeSet[b] { i++ continue } if start < i { e.Write(s[start:i]) } switch b { case '\\', '"': e.WriteByte('\\') e.WriteByte(b) case '\n': e.WriteByte('\\') e.WriteByte('n') case '\r': e.WriteByte('\\') e.WriteByte('r') case '\t': e.WriteByte('\\') e.WriteByte('t') default: // This encodes bytes < 0x20 except for \t, \n and \r. // If escapeHTML is set, it also escapes <, >, and & // because they can lead to security holes when // user-controlled strings are rendered into JSON // and served to some browsers. e.WriteString(`\u00`) e.WriteByte(hex[b>>4]) e.WriteByte(hex[b&0xF]) } i++ start = i continue } c, size := utf8.DecodeRune(s[i:]) if c == utf8.RuneError && size == 1 { if start < i { e.Write(s[start:i]) } e.WriteString(`\ufffd`) i += size start = i continue } // U+2028 is LINE SEPARATOR. // U+2029 is PARAGRAPH SEPARATOR. // They are both technically valid characters in JSON strings, // but don't work in JSONP, which has to be evaluated as JavaScript, // and can lead to security holes there. It is valid JSON to // escape them, so we do so unconditionally. // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. if c == '\u2028' || c == '\u2029' { if start < i { e.Write(s[start:i]) } e.WriteString(`\u202`) e.WriteByte(hex[c&0xF]) i += size start = i continue } i += size } if start < len(s) { e.Write(s[start:]) } e.WriteByte('"') } smithy-go-1.20.3/encoding/json/escape_test.go000066400000000000000000000017051463735525100211200ustar00rootroot00000000000000package json import ( "bytes" "testing" ) func TestEscapeStringBytes(t *testing.T) { cases := map[string]struct { expected string input []byte }{ "safeSet only": { expected: `"mountainPotato"`, input: []byte("mountainPotato"), }, "parenthesis": { expected: `"foo\""`, input: []byte(`foo"`), }, "double escape": { expected: `"hello\\\\world"`, input: []byte(`hello\\world`), }, "new line": { expected: `"foo\nbar"`, input: []byte("foo\nbar"), }, "carriage return": { expected: `"foo\rbar"`, input: []byte("foo\rbar"), }, "tab": { expected: `"foo\tbar"`, input: []byte("foo\tbar"), }, } for name, c := range cases { t.Run(name, func(t *testing.T) { var buffer bytes.Buffer escapeStringBytes(&buffer, c.input) expected := c.expected actual := buffer.String() if expected != actual { t.Errorf("\nexpected %v \nactual %v", expected, actual) } }) } } smithy-go-1.20.3/encoding/json/object.go000066400000000000000000000014521463735525100200660ustar00rootroot00000000000000package json import ( "bytes" ) // Object represents the encoding of a JSON Object type type Object struct { w *bytes.Buffer writeComma bool scratch *[]byte } func newObject(w *bytes.Buffer, scratch *[]byte) *Object { w.WriteRune(leftBrace) return &Object{w: w, scratch: scratch} } func (o *Object) writeKey(key string) { escapeStringBytes(o.w, []byte(key)) o.w.WriteRune(colon) } // Key adds the given named key to the JSON object. // Returns a Value encoder that should be used to encode // a JSON value type. func (o *Object) Key(name string) Value { if o.writeComma { o.w.WriteRune(comma) } else { o.writeComma = true } o.writeKey(name) return newValue(o.w, o.scratch) } // Close encodes the end of the JSON Object func (o *Object) Close() { o.w.WriteRune(rightBrace) } smithy-go-1.20.3/encoding/json/object_test.go000066400000000000000000000013521463735525100211240ustar00rootroot00000000000000package json import ( "bytes" "testing" ) func TestObject(t *testing.T) { buffer := bytes.NewBuffer(nil) scratch := make([]byte, 64) object := newObject(buffer, &scratch) object.Key("foo").String("bar") object.Key("faz").String("baz") object.Close() e := []byte(`{"foo":"bar","faz":"baz"}`) if a := buffer.Bytes(); bytes.Compare(e, a) != 0 { t.Errorf("expected %+q, but got %+q", e, a) } } func TestObjectKey_escaped(t *testing.T) { jsonEncoder := NewEncoder() object := jsonEncoder.Object() object.Key("foo\"").String("bar") object.Key("faz").String("baz") object.Close() e := []byte(`{"foo\"":"bar","faz":"baz"}`) if a := object.w.Bytes(); bytes.Compare(e, a) != 0 { t.Errorf("expected %+q, but got %+q", e, a) } } smithy-go-1.20.3/encoding/json/value.go000066400000000000000000000067251463735525100177440ustar00rootroot00000000000000package json import ( "bytes" "encoding/base64" "math/big" "strconv" "github.com/aws/smithy-go/encoding" ) // Value represents a JSON Value type // JSON Value types: Object, Array, String, Number, Boolean, and Null type Value struct { w *bytes.Buffer scratch *[]byte } // newValue returns a new Value encoder func newValue(w *bytes.Buffer, scratch *[]byte) Value { return Value{w: w, scratch: scratch} } // String encodes v as a JSON string func (jv Value) String(v string) { escapeStringBytes(jv.w, []byte(v)) } // Byte encodes v as a JSON number func (jv Value) Byte(v int8) { jv.Long(int64(v)) } // Short encodes v as a JSON number func (jv Value) Short(v int16) { jv.Long(int64(v)) } // Integer encodes v as a JSON number func (jv Value) Integer(v int32) { jv.Long(int64(v)) } // Long encodes v as a JSON number func (jv Value) Long(v int64) { *jv.scratch = strconv.AppendInt((*jv.scratch)[:0], v, 10) jv.w.Write(*jv.scratch) } // ULong encodes v as a JSON number func (jv Value) ULong(v uint64) { *jv.scratch = strconv.AppendUint((*jv.scratch)[:0], v, 10) jv.w.Write(*jv.scratch) } // Float encodes v as a JSON number func (jv Value) Float(v float32) { jv.float(float64(v), 32) } // Double encodes v as a JSON number func (jv Value) Double(v float64) { jv.float(v, 64) } func (jv Value) float(v float64, bits int) { *jv.scratch = encoding.EncodeFloat((*jv.scratch)[:0], v, bits) jv.w.Write(*jv.scratch) } // Boolean encodes v as a JSON boolean func (jv Value) Boolean(v bool) { *jv.scratch = strconv.AppendBool((*jv.scratch)[:0], v) jv.w.Write(*jv.scratch) } // Base64EncodeBytes writes v as a base64 value in JSON string func (jv Value) Base64EncodeBytes(v []byte) { encodeByteSlice(jv.w, (*jv.scratch)[:0], v) } // Write writes v directly to the JSON document func (jv Value) Write(v []byte) { jv.w.Write(v) } // Array returns a new Array encoder func (jv Value) Array() *Array { return newArray(jv.w, jv.scratch) } // Object returns a new Object encoder func (jv Value) Object() *Object { return newObject(jv.w, jv.scratch) } // Null encodes a null JSON value func (jv Value) Null() { jv.w.WriteString(null) } // BigInteger encodes v as JSON value func (jv Value) BigInteger(v *big.Int) { jv.w.Write([]byte(v.Text(10))) } // BigDecimal encodes v as JSON value func (jv Value) BigDecimal(v *big.Float) { if i, accuracy := v.Int64(); accuracy == big.Exact { jv.Long(i) return } // TODO: Should this try to match ES6 ToString similar to stdlib JSON? jv.w.Write([]byte(v.Text('e', -1))) } // Based on encoding/json encodeByteSlice from the Go Standard Library // https://golang.org/src/encoding/json/encode.go func encodeByteSlice(w *bytes.Buffer, scratch []byte, v []byte) { if v == nil { w.WriteString(null) return } w.WriteRune(quote) encodedLen := base64.StdEncoding.EncodedLen(len(v)) if encodedLen <= len(scratch) { // If the encoded bytes fit in e.scratch, avoid an extra // allocation and use the cheaper Encoding.Encode. dst := scratch[:encodedLen] base64.StdEncoding.Encode(dst, v) w.Write(dst) } else if encodedLen <= 1024 { // The encoded bytes are short enough to allocate for, and // Encoding.Encode is still cheaper. dst := make([]byte, encodedLen) base64.StdEncoding.Encode(dst, v) w.Write(dst) } else { // The encoded bytes are too long to cheaply allocate, and // Encoding.Encode is no longer noticeably cheaper. enc := base64.NewEncoder(base64.StdEncoding, w) enc.Write(v) enc.Close() } w.WriteRune(quote) } smithy-go-1.20.3/encoding/json/value_test.go000066400000000000000000000063111463735525100207720ustar00rootroot00000000000000package json import ( "bytes" "math" "math/big" "strconv" "testing" ) var ( oneInt = new(big.Int).SetInt64(1) oneFloat = new(big.Float).SetFloat64(1.0) ) func TestValue(t *testing.T) { cases := map[string]struct { setter func(Value) expected string }{ "string value": { setter: func(value Value) { value.String("foo") }, expected: `"foo"`, }, "string escaped": { setter: func(value Value) { value.String(`{"foo":"bar"}`) }, expected: `"{\"foo\":\"bar\"}"`, }, "integer": { setter: func(value Value) { value.Long(1024) }, expected: `1024`, }, "float": { setter: func(value Value) { value.Double(1e20) }, expected: `100000000000000000000`, }, "float exponent component": { setter: func(value Value) { value.Double(3e22) }, expected: `3e+22`, }, "boolean true": { setter: func(value Value) { value.Boolean(true) }, expected: `true`, }, "boolean false": { setter: func(value Value) { value.Boolean(false) }, expected: `false`, }, "encode bytes": { setter: func(value Value) { value.Base64EncodeBytes([]byte("foo bar")) }, expected: `"Zm9vIGJhcg=="`, }, "encode bytes nil": { setter: func(value Value) { value.Base64EncodeBytes(nil) }, expected: `null`, }, "object": { setter: func(value Value) { o := value.Object() defer o.Close() o.Key("key").String("value") }, expected: `{"key":"value"}`, }, "array": { setter: func(value Value) { o := value.Array() defer o.Close() o.Value().String("value1") o.Value().String("value2") }, expected: `["value1","value2"]`, }, "null": { setter: func(value Value) { value.Null() }, expected: `null`, }, "write bytes": { setter: func(value Value) { o := value.Object() o.Key("inline").Write([]byte(`{"nested":"value"}`)) defer o.Close() }, expected: `{"inline":{"nested":"value"}}`, }, "bigInteger": { setter: func(value Value) { v := new(big.Int).SetInt64(math.MaxInt64) value.BigInteger(v.Sub(v, oneInt)) }, expected: strconv.FormatInt(math.MaxInt64-1, 10), }, "bigInteger > int64": { setter: func(value Value) { v := new(big.Int).SetInt64(math.MaxInt64) value.BigInteger(v.Add(v, oneInt)) }, expected: "9223372036854775808", }, "bigInteger < int64": { setter: func(value Value) { v := new(big.Int).SetInt64(math.MinInt64) value.BigInteger(v.Sub(v, oneInt)) }, expected: "-9223372036854775809", }, "bigFloat": { setter: func(value Value) { v := new(big.Float).SetFloat64(math.MaxFloat64) value.BigDecimal(v.Sub(v, oneFloat)) }, expected: strconv.FormatFloat(math.MaxFloat64-1, 'e', -1, 64), }, "bigFloat fits in int64": { setter: func(value Value) { v := new(big.Float).SetInt64(math.MaxInt64) value.BigDecimal(v) }, expected: "9223372036854775807", }, } scratch := make([]byte, 64) for name, tt := range cases { t.Run(name, func(t *testing.T) { var b bytes.Buffer value := newValue(&b, &scratch) tt.setter(value) if e, a := []byte(tt.expected), b.Bytes(); bytes.Compare(e, a) != 0 { t.Errorf("expected %+q, but got %+q", e, a) } }) } } smithy-go-1.20.3/encoding/xml/000077500000000000000000000000001463735525100161165ustar00rootroot00000000000000smithy-go-1.20.3/encoding/xml/array.go000066400000000000000000000027211463735525100175650ustar00rootroot00000000000000package xml // arrayMemberWrapper is the default member wrapper tag name for XML Array type var arrayMemberWrapper = StartElement{ Name: Name{Local: "member"}, } // Array represents the encoding of a XML array type type Array struct { w writer scratch *[]byte // member start element is the array member wrapper start element memberStartElement StartElement // isFlattened indicates if the array is a flattened array. isFlattened bool } // newArray returns an array encoder. // It also takes in the member start element, array start element. // It takes in a isFlattened bool, indicating that an array is flattened array. // // A wrapped array ["value1", "value2"] is represented as // `value1value2`. // A flattened array `someList: ["value1", "value2"]` is represented as // `value1value2`. func newArray(w writer, scratch *[]byte, memberStartElement StartElement, arrayStartElement StartElement, isFlattened bool) *Array { var memberWrapper = memberStartElement if isFlattened { memberWrapper = arrayStartElement } return &Array{ w: w, scratch: scratch, memberStartElement: memberWrapper, isFlattened: isFlattened, } } // Member adds a new member to the XML array. // It returns a Value encoder. func (a *Array) Member() Value { v := newValue(a.w, a.scratch, a.memberStartElement) v.isFlattened = a.isFlattened return v } smithy-go-1.20.3/encoding/xml/array_test.go000066400000000000000000000025341463735525100206260ustar00rootroot00000000000000package xml import ( "bytes" "testing" ) func TestWrappedArray(t *testing.T) { buffer := bytes.NewBuffer(nil) scratch := make([]byte, 64) root := StartElement{Name: Name{Local: "array"}} a := newArray(buffer, &scratch, arrayMemberWrapper, root, false) a.Member().String("bar") a.Member().String("baz") e := []byte(`barbaz`) if a := buffer.Bytes(); bytes.Compare(e, a) != 0 { t.Errorf("expected %+q, but got %+q", e, a) } } func TestWrappedArrayWithCustomName(t *testing.T) { buffer := bytes.NewBuffer(nil) scratch := make([]byte, 64) root := StartElement{Name: Name{Local: "array"}} item := StartElement{Name: Name{Local: "item"}} a := newArray(buffer, &scratch, item, root, false) a.Member().String("bar") a.Member().String("baz") e := []byte(`barbaz`) if a := buffer.Bytes(); bytes.Compare(e, a) != 0 { t.Errorf("expected %+q, but got %+q", e, a) } } func TestFlattenedArray(t *testing.T) { buffer := bytes.NewBuffer(nil) scratch := make([]byte, 64) root := StartElement{Name: Name{Local: "array"}} a := newArray(buffer, &scratch, arrayMemberWrapper, root, true) a.Member().String("bar") a.Member().String("bix") e := []byte(`barbix`) if a := buffer.Bytes(); bytes.Compare(e, a) != 0 { t.Errorf("expected %+q, but got %+q", e, a) } } smithy-go-1.20.3/encoding/xml/constants.go000066400000000000000000000002551463735525100204630ustar00rootroot00000000000000package xml const ( leftAngleBracket = '<' rightAngleBracket = '>' forwardSlash = '/' colon = ':' equals = '=' quote = '"' ) smithy-go-1.20.3/encoding/xml/doc.go000066400000000000000000000040211463735525100172070ustar00rootroot00000000000000/* Package xml holds the XMl encoder utility. This utility is written in accordance to our design to delegate to shape serializer function in which a xml.Value will be passed around. Resources followed: https://smithy.io/2.0/spec/protocol-traits.html#xml-bindings Member Element Member element should be used to encode xml shapes into xml elements except for flattened xml shapes. Member element write their own element start tag. These elements should always be closed. Flattened Element Flattened element should be used to encode shapes marked with flattened trait into xml elements. Flattened element do not write a start tag, and thus should not be closed. Simple types encoding All simple type methods on value such as String(), Long() etc; auto close the associated member element. Array Array returns the collection encoder. It has two modes, wrapped and flattened encoding. Wrapped arrays have two methods Array() and ArrayWithCustomName() which facilitate array member wrapping. By default, a wrapped array members are wrapped with `member` named start element. appletree Flattened arrays rely on Value being marked as flattened. If a shape is marked as flattened, Array() will use the shape element name as wrapper for array elements. appletree Map Map is the map encoder. It has two modes, wrapped and flattened encoding. Wrapped map has Array() method, which facilitate map member wrapping. By default, a wrapped map members are wrapped with `entry` named start element. appletreesnowice Flattened map rely on Value being marked as flattened. If a shape is marked as flattened, Map() will use the shape element name as wrapper for map entry elements. appletreesnowice */ package xml smithy-go-1.20.3/encoding/xml/element.go000066400000000000000000000041761463735525100201060ustar00rootroot00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Copied and modified from Go 1.14 stdlib's encoding/xml package xml // A Name represents an XML name (Local) annotated // with a name space identifier (Space). // In tokens returned by Decoder.Token, the Space identifier // is given as a canonical URL, not the short prefix used // in the document being parsed. type Name struct { Space, Local string } // An Attr represents an attribute in an XML element (Name=Value). type Attr struct { Name Name Value string } /* NewAttribute returns a pointer to an attribute. It takes in a local name aka attribute name, and value representing the attribute value. */ func NewAttribute(local, value string) Attr { return Attr{ Name: Name{ Local: local, }, Value: value, } } /* NewNamespaceAttribute returns a pointer to an attribute. It takes in a local name aka attribute name, and value representing the attribute value. NewNamespaceAttribute appends `xmlns:` in front of namespace prefix. For creating a name space attribute representing `xmlns:prefix="http://example.com`, the breakdown would be: local = "prefix" value = "http://example.com" */ func NewNamespaceAttribute(local, value string) Attr { attr := NewAttribute(local, value) // default name space identifier attr.Name.Space = "xmlns" return attr } // A StartElement represents an XML start element. type StartElement struct { Name Name Attr []Attr } // Copy creates a new copy of StartElement. func (e StartElement) Copy() StartElement { attrs := make([]Attr, len(e.Attr)) copy(attrs, e.Attr) e.Attr = attrs return e } // End returns the corresponding XML end element. func (e StartElement) End() EndElement { return EndElement{e.Name} } // returns true if start element local name is empty func (e StartElement) isZero() bool { return len(e.Name.Local) == 0 } // An EndElement represents an XML end element. type EndElement struct { Name Name } // returns true if end element local name is empty func (e EndElement) isZero() bool { return len(e.Name.Local) == 0 } smithy-go-1.20.3/encoding/xml/encoder.go000066400000000000000000000025461463735525100200730ustar00rootroot00000000000000package xml // writer interface used by the xml encoder to write an encoded xml // document in a writer. type writer interface { // Write takes in a byte slice and returns number of bytes written and error Write(p []byte) (n int, err error) // WriteRune takes in a rune and returns number of bytes written and error WriteRune(r rune) (n int, err error) // WriteString takes in a string and returns number of bytes written and error WriteString(s string) (n int, err error) // String method returns a string String() string // Bytes return a byte slice. Bytes() []byte } // Encoder is an XML encoder that supports construction of XML values // using methods. The encoder takes in a writer and maintains a scratch buffer. type Encoder struct { w writer scratch *[]byte } // NewEncoder returns an XML encoder func NewEncoder(w writer) *Encoder { scratch := make([]byte, 64) return &Encoder{w: w, scratch: &scratch} } // String returns the string output of the XML encoder func (e Encoder) String() string { return e.w.String() } // Bytes returns the []byte slice of the XML encoder func (e Encoder) Bytes() []byte { return e.w.Bytes() } // RootElement builds a root element encoding // It writes it's start element tag. The value should be closed. func (e Encoder) RootElement(element StartElement) Value { return newValue(e.w, e.scratch, element) } smithy-go-1.20.3/encoding/xml/encoder_test.go000066400000000000000000000341451463735525100211320ustar00rootroot00000000000000package xml_test import ( "bytes" "log" "sort" "testing" "github.com/aws/smithy-go/encoding/xml" ) var root = xml.StartElement{Name: xml.Name{Local: "root"}} func TestEncoder(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { root := encoder.RootElement(root) defer root.Close() stringKey := xml.StartElement{Name: xml.Name{Local: "stringKey"}} integerKey := xml.StartElement{Name: xml.Name{Local: "integerKey"}} floatKey := xml.StartElement{Name: xml.Name{Local: "floatKey"}} foo := xml.StartElement{Name: xml.Name{Local: "foo"}} byteSlice := xml.StartElement{Name: xml.Name{Local: "byteSlice"}} root.MemberElement(stringKey).String("stringValue") root.MemberElement(integerKey).Integer(1024) root.MemberElement(floatKey).Float(3.14) ns := root.MemberElement(foo) defer ns.Close() ns.MemberElement(byteSlice).String("Zm9vIGJhcg==") }() e := []byte(`stringValue10243.14Zm9vIGJhcg==`) verify(t, encoder, e) } func TestEncodeAttribute(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := xml.StartElement{ Name: xml.Name{Local: "payload", Space: "baz"}, Attr: []xml.Attr{ xml.NewAttribute("attrkey", "value"), }, } obj := encoder.RootElement(r) obj.String("") }() expect := `` verify(t, encoder, []byte(expect)) } func TestEncodeNamespace(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { root := encoder.RootElement(root) defer root.Close() key := xml.StartElement{ Name: xml.Name{Local: "namespace"}, Attr: []xml.Attr{ xml.NewNamespaceAttribute("prefix", "https://example.com"), }, } n := root.MemberElement(key) defer n.Close() prefix := xml.StartElement{Name: xml.Name{Local: "user"}} n.MemberElement(prefix).String("abc") }() e := []byte(`abc`) verify(t, encoder, e) } func TestEncodeEmptyNamespacePrefix(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { root := encoder.RootElement(root) defer root.Close() key := xml.StartElement{ Name: xml.Name{Local: "namespace"}, Attr: []xml.Attr{ xml.NewNamespaceAttribute("", "https://example.com"), }, } n := root.MemberElement(key) defer n.Close() prefix := xml.StartElement{Name: xml.Name{Local: "user"}} n.MemberElement(prefix).String("abc") }() e := []byte(`abc`) verify(t, encoder, e) } func verify(t *testing.T, encoder *xml.Encoder, e []byte) { if a := encoder.Bytes(); bytes.Compare(e, a) != 0 { t.Errorf("expected %+q, but got %+q", e, a) } if a := encoder.String(); string(encoder.Bytes()) != a { t.Errorf("expected %s, but got %s", e, a) } } func TestEncodeNestedShape(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // nested `nested` shape nested := xml.StartElement{Name: xml.Name{Local: "nested"}} n1 := r.MemberElement(nested) defer n1.Close() // nested `value` shape value := xml.StartElement{Name: xml.Name{Local: "value"}} n1.MemberElement(value).String("expected value") }() e := []byte(`expected value`) defer verify(t, encoder, e) } func TestEncodeMapString(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // nested `mapStr` shape mapstr := xml.StartElement{Name: xml.Name{Local: "mapstr"}} mapElement := r.MemberElement(mapstr) defer mapElement.Close() m := mapElement.Map() key := xml.StartElement{Name: xml.Name{Local: "key"}} value := xml.StartElement{Name: xml.Name{Local: "value"}} e := m.Entry() defer e.Close() e.MemberElement(key).String("abc") e.MemberElement(value).Integer(123) }() ex := []byte(`abc123`) verify(t, encoder, ex) } func TestEncodeMapFlatten(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // nested `mapStr` shape mapstr := xml.StartElement{Name: xml.Name{Local: "mapstr"}} flatElement := r.FlattenedElement(mapstr) m := flatElement.Map() e := m.Entry() defer e.Close() key := xml.StartElement{Name: xml.Name{Local: "key"}} e.MemberElement(key).String("abc") value := xml.StartElement{Name: xml.Name{Local: "value"}} e.MemberElement(value).Integer(123) }() ex := []byte(`abc123`) verify(t, encoder, ex) } func TestEncodeMapNamed(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // nested `mapStr` shape mapstr := xml.StartElement{Name: xml.Name{Local: "mapNamed"}} mapElement := r.MemberElement(mapstr) defer mapElement.Close() m := mapElement.Map() e := m.Entry() defer e.Close() key := xml.StartElement{Name: xml.Name{Local: "namedKey"}} e.MemberElement(key).String("abc") value := xml.StartElement{Name: xml.Name{Local: "namedValue"}} e.MemberElement(value).Integer(123) }() ex := []byte(`abc123`) verify(t, encoder, ex) } func TestEncodeMapShape(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // nested `mapStr` shape mapstr := xml.StartElement{Name: xml.Name{Local: "mapShape"}} mapElement := r.MemberElement(mapstr) defer mapElement.Close() m := mapElement.Map() e := m.Entry() defer e.Close() key := xml.StartElement{Name: xml.Name{Local: "key"}} e.MemberElement(key).String("abc") value := xml.StartElement{Name: xml.Name{Local: "value"}} n1 := e.MemberElement(value) defer n1.Close() shapeVal := xml.StartElement{Name: xml.Name{Local: "shapeVal"}} n1.MemberElement(shapeVal).Integer(1) }() ex := []byte(`abc1`) verify(t, encoder, ex) } func TestEncodeMapFlattenShape(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // nested `mapStr` shape mapstr := xml.StartElement{Name: xml.Name{Local: "mapShape"}} flatElement := r.FlattenedElement(mapstr) m := flatElement.Map() e := m.Entry() defer e.Close() key := xml.StartElement{Name: xml.Name{Local: "key"}} e.MemberElement(key).String("abc") value := xml.StartElement{Name: xml.Name{Local: "value"}} n1 := e.MemberElement(value) defer n1.Close() shapeVal := xml.StartElement{Name: xml.Name{Local: "shapeVal"}} n1.MemberElement(shapeVal).Integer(1) }() ex := []byte(`abc1`) verify(t, encoder, ex) } func TestEncodeMapNamedShape(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // nested `mapStr` shape mapstr := xml.StartElement{Name: xml.Name{Local: "mapNamedShape"}} mapElement := r.MemberElement(mapstr) defer mapElement.Close() m := mapElement.Map() e := m.Entry() defer e.Close() key := xml.StartElement{Name: xml.Name{Local: "namedKey"}} e.MemberElement(key).String("abc") value := xml.StartElement{Name: xml.Name{Local: "namedValue"}} n1 := e.MemberElement(value) defer n1.Close() shapeVal := xml.StartElement{Name: xml.Name{Local: "shapeVal"}} n1.MemberElement(shapeVal).Integer(1) }() ex := []byte(`abc1`) verify(t, encoder, ex) } func TestEncodeListString(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // Object key `liststr` liststr := xml.StartElement{Name: xml.Name{Local: "liststr"}} m := r.MemberElement(liststr) defer m.Close() a := m.Array() a.Member().String("abc") a.Member().Integer(123) }() ex := []byte(`abc123`) verify(t, encoder, ex) } func TestEncodeListFlatten(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // Object key `liststr` liststr := xml.StartElement{Name: xml.Name{Local: "liststr"}} m := r.FlattenedElement(liststr) a := m.Array() a.Member().String("abc") a.Member().Integer(123) }() ex := []byte(`abc123`) verify(t, encoder, ex) } func TestEncodeListNamed(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // Object key `liststr` liststr := xml.StartElement{Name: xml.Name{Local: "liststr"}} namedMember := xml.StartElement{Name: xml.Name{Local: "namedMember"}} m := r.MemberElement(liststr) defer m.Close() a := m.ArrayWithCustomName(namedMember) a.Member().String("abc") a.Member().Integer(123) }() ex := []byte(`abc123`) verify(t, encoder, ex) } // func TestEncodeListShape(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // Object key `liststr` liststr := xml.StartElement{Name: xml.Name{Local: "liststr"}} m := r.MemberElement(liststr) defer m.Close() a := m.Array() value := xml.StartElement{Name: xml.Name{Local: "value"}} m1 := a.Member() m1.MemberElement(value).String("abc") m1.Close() m2 := a.Member() m2.MemberElement(value).Integer(123) m2.Close() }() ex := []byte(`abc123`) verify(t, encoder, ex) } // func TestEncodeListFlattenShape(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // Object key `liststr` liststr := xml.StartElement{Name: xml.Name{Local: "liststr"}} m := r.FlattenedElement(liststr) a := m.Array() value := xml.StartElement{Name: xml.Name{Local: "value"}} m1 := a.Member() m1.MemberElement(value).String("abc") m1.Close() m2 := a.Member() m2.MemberElement(value).Integer(123) m2.Close() }() ex := []byte(`abc123`) verify(t, encoder, ex) } // func TestEncodeListNamedShape(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() // Object key `liststr` liststr := xml.StartElement{Name: xml.Name{Local: "liststr"}} namedMember := xml.StartElement{Name: xml.Name{Local: "namedMember"}} // member element m := r.MemberElement(liststr) defer m.Close() // Build array a := m.ArrayWithCustomName(namedMember) value := xml.StartElement{Name: xml.Name{Local: "value"}} m1 := a.Member() m1.MemberElement(value).String("abc") m1.Close() m2 := a.Member() m2.MemberElement(value).Integer(123) m2.Close() }() ex := []byte(`abc123`) verify(t, encoder, ex) } func TestEncodeEscaping(t *testing.T) { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) func() { r := encoder.RootElement(root) defer r.Close() cases := map[string]rune{ "quote": '"', "apos": '\'', "amp": '&', "lt": '<', "gt": '>', "tab": '\t', "newLine": '\n', "carriageReturn": '\r', "nextLine": '\u0085', "lineSeparator": '\u2028', } var sortedKeys []string for name := range cases { sortedKeys = append(sortedKeys, name) } sort.Strings(sortedKeys) for _, name := range sortedKeys { rr := cases[name] st := xml.StartElement{Name: xml.Name{Local: name}} st.Attr = append(st.Attr, xml.Attr{ Name: xml.Name{ Local: "key", }, Value: name + string(rr) + name, }) value := r.MemberElement(st) value.String(name + string(rr) + name) } }() ex := []byte(`amp&ampapos'aposcarriageReturn carriageReturngt>gtlineSeparator
lineSeparatorlt<ltnewLine newLinenextLine…nextLinequote"quotetab tab`) verify(t, encoder, ex) } // ExampleEncoder is the example function on how to use an encoder func ExampleEncoder() { b := bytes.NewBuffer(nil) encoder := xml.NewEncoder(b) // expected encoded xml document is : // `abc123` defer log.Printf("Encoded xml document: %v", encoder.String()) r := encoder.RootElement(root) defer r.Close() // Object key `liststr` liststr := xml.StartElement{Name: xml.Name{Local: "liststr"}} namedMember := xml.StartElement{Name: xml.Name{Local: "namedMember"}} // member element m := r.MemberElement(liststr) defer m.Close() // Build array a := m.ArrayWithCustomName(namedMember) value := xml.StartElement{Name: xml.Name{Local: "value"}} m1 := a.Member() m1.MemberElement(value).String("abc") m1.Close() m2 := a.Member() m2.MemberElement(value).Integer(123) m2.Close() } smithy-go-1.20.3/encoding/xml/error_utils.go000066400000000000000000000027241463735525100210230ustar00rootroot00000000000000package xml import ( "encoding/xml" "fmt" "io" ) // ErrorComponents represents the error response fields // that will be deserialized from an xml error response body type ErrorComponents struct { Code string Message string } // GetErrorResponseComponents returns the error fields from an xml error response body func GetErrorResponseComponents(r io.Reader, noErrorWrapping bool) (ErrorComponents, error) { if noErrorWrapping { var errResponse noWrappedErrorResponse if err := xml.NewDecoder(r).Decode(&errResponse); err != nil && err != io.EOF { return ErrorComponents{}, fmt.Errorf("error while deserializing xml error response: %w", err) } return ErrorComponents{ Code: errResponse.Code, Message: errResponse.Message, }, nil } var errResponse wrappedErrorResponse if err := xml.NewDecoder(r).Decode(&errResponse); err != nil && err != io.EOF { return ErrorComponents{}, fmt.Errorf("error while deserializing xml error response: %w", err) } return ErrorComponents{ Code: errResponse.Code, Message: errResponse.Message, }, nil } // noWrappedErrorResponse represents the error response body with // no internal ... type wrappedErrorResponse struct { Code string `xml:"Error>Code"` Message string `xml:"Error>Message"` } smithy-go-1.20.3/encoding/xml/error_utils_test.go000066400000000000000000000031131463735525100220530ustar00rootroot00000000000000package xml import ( "bytes" "io" "strings" "testing" ) func TestGetResponseErrorCode(t *testing.T) { cases := map[string]struct { errorResponse io.Reader noErrorWrappingEnabled bool expectedErrorCode string expectedErrorMessage string }{ "no error wrapping enabled": { errorResponse: bytes.NewReader([]byte(` Sender InvalidGreeting Hi setting foo-id `)), expectedErrorCode: "InvalidGreeting", expectedErrorMessage: "Hi", }, "no error wrapping disabled": { errorResponse: bytes.NewReader([]byte(` Sender InvalidGreeting Hi setting foo-id `)), noErrorWrappingEnabled: true, expectedErrorCode: "InvalidGreeting", expectedErrorMessage: "Hi", }, "no response body": { errorResponse: bytes.NewReader([]byte(``)), }, } for name, c := range cases { t.Run(name, func(t *testing.T) { ec, err := GetErrorResponseComponents(c.errorResponse, c.noErrorWrappingEnabled) if err != nil { t.Fatalf("expected no error, got %v", err) } if e, a := c.expectedErrorCode, ec.Code; !strings.EqualFold(e, a) { t.Fatalf("expected %v, got %v", e, a) } if e, a := c.expectedErrorMessage, ec.Message; !strings.EqualFold(e, a) { t.Fatalf("expected %v, got %v", e, a) } }) } } smithy-go-1.20.3/encoding/xml/escape.go000066400000000000000000000057641463735525100177210ustar00rootroot00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Copied and modified from Go 1.14 stdlib's encoding/xml package xml import ( "unicode/utf8" ) // Copied from Go 1.14 stdlib's encoding/xml var ( escQuot = []byte(""") // shorter than """ escApos = []byte("'") // shorter than "'" escAmp = []byte("&") escLT = []byte("<") escGT = []byte(">") escTab = []byte(" ") escNL = []byte(" ") escCR = []byte(" ") escFFFD = []byte("\uFFFD") // Unicode replacement character // Additional Escapes escNextLine = []byte("…") escLS = []byte("
") ) // Decide whether the given rune is in the XML Character Range, per // the Char production of https://www.xml.com/axml/testaxml.htm, // Section 2.2 Characters. func isInCharacterRange(r rune) (inrange bool) { return r == 0x09 || r == 0x0A || r == 0x0D || r >= 0x20 && r <= 0xD7FF || r >= 0xE000 && r <= 0xFFFD || r >= 0x10000 && r <= 0x10FFFF } // TODO: When do we need to escape the string? // Based on encoding/xml escapeString from the Go Standard Library. // https://golang.org/src/encoding/xml/xml.go func escapeString(e writer, s string) { var esc []byte last := 0 for i := 0; i < len(s); { r, width := utf8.DecodeRuneInString(s[i:]) i += width switch r { case '"': esc = escQuot case '\'': esc = escApos case '&': esc = escAmp case '<': esc = escLT case '>': esc = escGT case '\t': esc = escTab case '\n': esc = escNL case '\r': esc = escCR case '\u0085': // Not escaped by stdlib esc = escNextLine case '\u2028': // Not escaped by stdlib esc = escLS default: if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) { esc = escFFFD break } continue } e.WriteString(s[last : i-width]) e.Write(esc) last = i } e.WriteString(s[last:]) } // escapeText writes to w the properly escaped XML equivalent // of the plain text data s. If escapeNewline is true, newline // characters will be escaped. // // Based on encoding/xml escapeText from the Go Standard Library. // https://golang.org/src/encoding/xml/xml.go func escapeText(e writer, s []byte) { var esc []byte last := 0 for i := 0; i < len(s); { r, width := utf8.DecodeRune(s[i:]) i += width switch r { case '"': esc = escQuot case '\'': esc = escApos case '&': esc = escAmp case '<': esc = escLT case '>': esc = escGT case '\t': esc = escTab case '\n': // This always escapes newline, which is different than stdlib's optional // escape of new line. esc = escNL case '\r': esc = escCR case '\u0085': // Not escaped by stdlib esc = escNextLine case '\u2028': // Not escaped by stdlib esc = escLS default: if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) { esc = escFFFD break } continue } e.Write(s[last : i-width]) e.Write(esc) last = i } e.Write(s[last:]) } smithy-go-1.20.3/encoding/xml/map.go000066400000000000000000000030021463735525100172150ustar00rootroot00000000000000package xml // mapEntryWrapper is the default member wrapper start element for XML Map entry var mapEntryWrapper = StartElement{ Name: Name{Local: "entry"}, } // Map represents the encoding of a XML map type type Map struct { w writer scratch *[]byte // member start element is the map entry wrapper start element memberStartElement StartElement // isFlattened returns true if the map is a flattened map isFlattened bool } // newMap returns a map encoder which sets the default map // entry wrapper to `entry`. // // A map `someMap : {{key:"abc", value:"123"}}` is represented as // `abc123`. func newMap(w writer, scratch *[]byte) *Map { return &Map{ w: w, scratch: scratch, memberStartElement: mapEntryWrapper, } } // newFlattenedMap returns a map encoder which sets the map // entry wrapper to the passed in memberWrapper`. // // A flattened map `someMap : {{key:"abc", value:"123"}}` is represented as // `abc123`. func newFlattenedMap(w writer, scratch *[]byte, memberWrapper StartElement) *Map { return &Map{ w: w, scratch: scratch, memberStartElement: memberWrapper, isFlattened: true, } } // Entry returns a Value encoder with map's element. // It writes the member wrapper start tag for each entry. func (m *Map) Entry() Value { v := newValue(m.w, m.scratch, m.memberStartElement) v.isFlattened = m.isFlattened return v } smithy-go-1.20.3/encoding/xml/map_test.go000066400000000000000000000040061463735525100202610ustar00rootroot00000000000000package xml import ( "bytes" "testing" ) func TestWrappedMap(t *testing.T) { buffer := bytes.NewBuffer(nil) scratch := make([]byte, 64) func() { m := newMap(buffer, &scratch) key := StartElement{Name: Name{Local: "key"}} value := StartElement{Name: Name{Local: "value"}} // map entry e := m.Entry() e.MemberElement(key).String("example-key1") e.MemberElement(value).String("example1") e.Close() // map entry e = m.Entry() e.MemberElement(key).String("example-key2") e.MemberElement(value).String("example2") e.Close() // map entry e = m.Entry() e.MemberElement(key).String("example-key3") e.MemberElement(value).String("example3") e.Close() }() ex := []byte(`example-key1example1example-key2example2example-key3example3`) if a := buffer.Bytes(); bytes.Compare(ex, a) != 0 { t.Errorf("expected %+q, but got %+q", ex, a) } } func TestFlattenedMapWithCustomName(t *testing.T) { buffer := bytes.NewBuffer(nil) scratch := make([]byte, 64) func() { root := StartElement{Name: Name{Local: "flatMap"}} m := newFlattenedMap(buffer, &scratch, root) key := StartElement{Name: Name{Local: "key"}} value := StartElement{Name: Name{Local: "value"}} // map entry e := m.Entry() e.MemberElement(key).String("example-key1") e.MemberElement(value).String("example1") e.Close() // map entry e = m.Entry() e.MemberElement(key).String("example-key2") e.MemberElement(value).String("example2") e.Close() // map entry e = m.Entry() e.MemberElement(key).String("example-key3") e.MemberElement(value).String("example3") e.Close() }() ex := []byte(`example-key1example1example-key2example2example-key3example3`) if a := buffer.Bytes(); bytes.Compare(ex, a) != 0 { t.Errorf("expected %+q, but got %+q", ex, a) } } smithy-go-1.20.3/encoding/xml/value.go000066400000000000000000000202001463735525100175530ustar00rootroot00000000000000package xml import ( "encoding/base64" "fmt" "math/big" "strconv" "github.com/aws/smithy-go/encoding" ) // Value represents an XML Value type // XML Value types: Object, Array, Map, String, Number, Boolean. type Value struct { w writer scratch *[]byte // xml start element is the associated start element for the Value startElement StartElement // indicates if the Value represents a flattened shape isFlattened bool } // newFlattenedValue returns a Value encoder. newFlattenedValue does NOT write the start element tag func newFlattenedValue(w writer, scratch *[]byte, startElement StartElement) Value { return Value{ w: w, scratch: scratch, startElement: startElement, } } // newValue writes the start element xml tag and returns a Value func newValue(w writer, scratch *[]byte, startElement StartElement) Value { writeStartElement(w, startElement) return Value{w: w, scratch: scratch, startElement: startElement} } // writeStartElement takes in a start element and writes it. // It handles namespace, attributes in start element. func writeStartElement(w writer, el StartElement) error { if el.isZero() { return fmt.Errorf("xml start element cannot be nil") } w.WriteRune(leftAngleBracket) if len(el.Name.Space) != 0 { escapeString(w, el.Name.Space) w.WriteRune(colon) } escapeString(w, el.Name.Local) for _, attr := range el.Attr { w.WriteRune(' ') writeAttribute(w, &attr) } w.WriteRune(rightAngleBracket) return nil } // writeAttribute writes an attribute from a provided Attribute // For a namespace attribute, the attr.Name.Space must be defined as "xmlns". // https://www.w3.org/TR/REC-xml-names/#NT-DefaultAttName func writeAttribute(w writer, attr *Attr) { // if local, space both are not empty if len(attr.Name.Space) != 0 && len(attr.Name.Local) != 0 { escapeString(w, attr.Name.Space) w.WriteRune(colon) } // if prefix is empty, the default `xmlns` space should be used as prefix. if len(attr.Name.Local) == 0 { attr.Name.Local = attr.Name.Space } escapeString(w, attr.Name.Local) w.WriteRune(equals) w.WriteRune(quote) escapeString(w, attr.Value) w.WriteRune(quote) } // writeEndElement takes in a end element and writes it. func writeEndElement(w writer, el EndElement) error { if el.isZero() { return fmt.Errorf("xml end element cannot be nil") } w.WriteRune(leftAngleBracket) w.WriteRune(forwardSlash) if len(el.Name.Space) != 0 { escapeString(w, el.Name.Space) w.WriteRune(colon) } escapeString(w, el.Name.Local) w.WriteRune(rightAngleBracket) return nil } // String encodes v as a XML string. // It will auto close the parent xml element tag. func (xv Value) String(v string) { escapeString(xv.w, v) xv.Close() } // Byte encodes v as a XML number. // It will auto close the parent xml element tag. func (xv Value) Byte(v int8) { xv.Long(int64(v)) } // Short encodes v as a XML number. // It will auto close the parent xml element tag. func (xv Value) Short(v int16) { xv.Long(int64(v)) } // Integer encodes v as a XML number. // It will auto close the parent xml element tag. func (xv Value) Integer(v int32) { xv.Long(int64(v)) } // Long encodes v as a XML number. // It will auto close the parent xml element tag. func (xv Value) Long(v int64) { *xv.scratch = strconv.AppendInt((*xv.scratch)[:0], v, 10) xv.w.Write(*xv.scratch) xv.Close() } // Float encodes v as a XML number. // It will auto close the parent xml element tag. func (xv Value) Float(v float32) { xv.float(float64(v), 32) xv.Close() } // Double encodes v as a XML number. // It will auto close the parent xml element tag. func (xv Value) Double(v float64) { xv.float(v, 64) xv.Close() } func (xv Value) float(v float64, bits int) { *xv.scratch = encoding.EncodeFloat((*xv.scratch)[:0], v, bits) xv.w.Write(*xv.scratch) } // Boolean encodes v as a XML boolean. // It will auto close the parent xml element tag. func (xv Value) Boolean(v bool) { *xv.scratch = strconv.AppendBool((*xv.scratch)[:0], v) xv.w.Write(*xv.scratch) xv.Close() } // Base64EncodeBytes writes v as a base64 value in XML string. // It will auto close the parent xml element tag. func (xv Value) Base64EncodeBytes(v []byte) { encodeByteSlice(xv.w, (*xv.scratch)[:0], v) xv.Close() } // BigInteger encodes v big.Int as XML value. // It will auto close the parent xml element tag. func (xv Value) BigInteger(v *big.Int) { xv.w.Write([]byte(v.Text(10))) xv.Close() } // BigDecimal encodes v big.Float as XML value. // It will auto close the parent xml element tag. func (xv Value) BigDecimal(v *big.Float) { if i, accuracy := v.Int64(); accuracy == big.Exact { xv.Long(i) return } xv.w.Write([]byte(v.Text('e', -1))) xv.Close() } // Write writes v directly to the xml document // if escapeXMLText is set to true, write will escape text. // It will auto close the parent xml element tag. func (xv Value) Write(v []byte, escapeXMLText bool) { // escape and write xml text if escapeXMLText { escapeText(xv.w, v) } else { // write xml directly xv.w.Write(v) } xv.Close() } // MemberElement does member element encoding. It returns a Value. // Member Element method should be used for all shapes except flattened shapes. // // A call to MemberElement will write nested element tags directly using the // provided start element. The value returned by MemberElement should be closed. func (xv Value) MemberElement(element StartElement) Value { return newValue(xv.w, xv.scratch, element) } // FlattenedElement returns flattened element encoding. It returns a Value. // This method should be used for flattened shapes. // // Unlike MemberElement, flattened element will NOT write element tags // directly for the associated start element. // // The value returned by the FlattenedElement does not need to be closed. func (xv Value) FlattenedElement(element StartElement) Value { v := newFlattenedValue(xv.w, xv.scratch, element) v.isFlattened = true return v } // Array returns an array encoder. By default, the members of array are // wrapped with `` element tag. // If value is marked as flattened, the start element is used to wrap the members instead of // the `` element. func (xv Value) Array() *Array { return newArray(xv.w, xv.scratch, arrayMemberWrapper, xv.startElement, xv.isFlattened) } /* ArrayWithCustomName returns an array encoder. It takes named start element as an argument, the named start element will used to wrap xml array entries. for eg, `entry1` Here `customName` named start element will be wrapped on each array member. */ func (xv Value) ArrayWithCustomName(element StartElement) *Array { return newArray(xv.w, xv.scratch, element, xv.startElement, xv.isFlattened) } /* Map returns a map encoder. By default, the map entries are wrapped with `` element tag. If value is marked as flattened, the start element is used to wrap the entry instead of the `` element. */ func (xv Value) Map() *Map { // flattened map if xv.isFlattened { return newFlattenedMap(xv.w, xv.scratch, xv.startElement) } // un-flattened map return newMap(xv.w, xv.scratch) } // encodeByteSlice is modified copy of json encoder's encodeByteSlice. // It is used to base64 encode a byte slice. func encodeByteSlice(w writer, scratch []byte, v []byte) { if v == nil { return } encodedLen := base64.StdEncoding.EncodedLen(len(v)) if encodedLen <= len(scratch) { // If the encoded bytes fit in e.scratch, avoid an extra // allocation and use the cheaper Encoding.Encode. dst := scratch[:encodedLen] base64.StdEncoding.Encode(dst, v) w.Write(dst) } else if encodedLen <= 1024 { // The encoded bytes are short enough to allocate for, and // Encoding.Encode is still cheaper. dst := make([]byte, encodedLen) base64.StdEncoding.Encode(dst, v) w.Write(dst) } else { // The encoded bytes are too long to cheaply allocate, and // Encoding.Encode is no longer noticeably cheaper. enc := base64.NewEncoder(base64.StdEncoding, w) enc.Write(v) enc.Close() } } // IsFlattened returns true if value is for flattened shape. func (xv Value) IsFlattened() bool { return xv.isFlattened } // Close closes the value. func (xv Value) Close() { writeEndElement(xv.w, xv.startElement.End()) } smithy-go-1.20.3/encoding/xml/value_test.go000066400000000000000000000117521463735525100206260ustar00rootroot00000000000000package xml import ( "bytes" "fmt" "math" "math/big" "strconv" "testing" ) var ( oneInt = new(big.Int).SetInt64(1) oneFloat = new(big.Float).SetFloat64(1.0) ) func TestValue(t *testing.T) { nested := StartElement{Name: Name{Local: "nested"}} cases := map[string]struct { setter func(Value) expected string }{ "string value": { setter: func(value Value) { value.String("foo") }, expected: `foo`, }, "string escaped": { setter: func(value Value) { value.String("{\"foo\":\"bar\"}") }, expected: fmt.Sprintf("{%sfoo%s:%sbar%s}", escQuot, escQuot, escQuot, escQuot), }, "integer": { setter: func(value Value) { value.Long(1024) }, expected: `1024`, }, "float": { setter: func(value Value) { value.Double(1e20) }, expected: `100000000000000000000`, }, "float exponent component": { setter: func(value Value) { value.Double(3e22) }, expected: `3e+22`, }, "boolean true": { setter: func(value Value) { value.Boolean(true) }, expected: `true`, }, "boolean false": { setter: func(value Value) { value.Boolean(false) }, expected: `false`, }, "encode bytes": { setter: func(value Value) { value.Base64EncodeBytes([]byte("foo bar")) }, expected: `Zm9vIGJhcg==`, }, "encode bytes nil": { setter: func(value Value) { value.Base64EncodeBytes(nil) }, expected: ``, }, "object": { setter: func(value Value) { defer value.Close() value.MemberElement(nested).String("value") }, expected: `value`, }, "null": { setter: func(value Value) { value.Close() }, expected: ``, }, "nullWithRoot": { setter: func(value Value) { defer value.Close() o := value.MemberElement(nested) defer o.Close() }, expected: ``, }, "write text": { setter: func(value Value) { defer value.Close() o := value.MemberElement(nested) o.Write([]byte(`{"nested":"value"}`), false) }, expected: `{"nested":"value"}`, }, "write escaped text": { setter: func(value Value) { defer value.Close() o := value.MemberElement(nested) o.Write([]byte(`{"nested":"value"}`), true) }, expected: fmt.Sprintf("{%snested%s:%svalue%s}", escQuot, escQuot, escQuot, escQuot), }, "bigInteger": { setter: func(value Value) { v := new(big.Int).SetInt64(math.MaxInt64) value.BigInteger(v.Sub(v, oneInt)) }, expected: strconv.FormatInt(math.MaxInt64-1, 10), }, "bigInteger > int64": { setter: func(value Value) { v := new(big.Int).SetInt64(math.MaxInt64) value.BigInteger(v.Add(v, oneInt)) }, expected: "9223372036854775808", }, "bigInteger < int64": { setter: func(value Value) { v := new(big.Int).SetInt64(math.MinInt64) value.BigInteger(v.Sub(v, oneInt)) }, expected: "-9223372036854775809", }, "bigFloat": { setter: func(value Value) { v := new(big.Float).SetFloat64(math.MaxFloat64) value.BigDecimal(v.Sub(v, oneFloat)) }, expected: strconv.FormatFloat(math.MaxFloat64-1, 'e', -1, 64), }, "bigFloat fits in int64": { setter: func(value Value) { v := new(big.Float).SetInt64(math.MaxInt64) value.BigDecimal(v) }, expected: "9223372036854775807", }, } scratch := make([]byte, 64) for name, tt := range cases { t.Run(name, func(t *testing.T) { b := bytes.NewBuffer(nil) root := StartElement{Name: Name{Local: "root"}} value := newValue(b, &scratch, root) tt.setter(value) if e, a := []byte(""+tt.expected+""), b.Bytes(); bytes.Compare(e, a) != 0 { t.Errorf("expected %+q, but got %+q", e, a) } }) } } func TestWrappedValue(t *testing.T) { buffer := bytes.NewBuffer(nil) scratch := make([]byte, 64) func() { root := StartElement{Name: Name{Local: "root"}} object := newValue(buffer, &scratch, root) defer object.Close() foo := StartElement{Name: Name{Local: "foo"}} faz := StartElement{Name: Name{Local: "faz"}} object.MemberElement(foo).String("bar") object.MemberElement(faz).String("baz") }() e := []byte(`barbaz`) if a := buffer.Bytes(); bytes.Compare(e, a) != 0 { t.Errorf("expected %+q, but got %+q", e, a) } } func TestWrappedValueWithNameSpaceAndAttributes(t *testing.T) { buffer := bytes.NewBuffer(nil) scratch := make([]byte, 64) func() { root := StartElement{Name: Name{Local: "root"}} object := newValue(buffer, &scratch, root) defer object.Close() foo := StartElement{Name: Name{Local: "foo"}, Attr: []Attr{ NewNamespaceAttribute("newspace", "https://endpoint.com"), NewAttribute("attrName", "attrValue"), }} faz := StartElement{Name: Name{Local: "faz"}} object.MemberElement(foo).String("bar") object.MemberElement(faz).String("baz") }() e := []byte(`barbaz`) if a := buffer.Bytes(); bytes.Compare(e, a) != 0 { t.Errorf("expected %+q, but got %+q", e, a) } } smithy-go-1.20.3/encoding/xml/xml_decoder.go000066400000000000000000000104461463735525100207370ustar00rootroot00000000000000package xml import ( "encoding/xml" "fmt" "strings" ) // NodeDecoder is a XML decoder wrapper that is responsible to decoding // a single XML Node element and it's nested member elements. This wrapper decoder // takes in the start element of the top level node being decoded. type NodeDecoder struct { Decoder *xml.Decoder StartEl xml.StartElement } // WrapNodeDecoder returns an initialized XMLNodeDecoder func WrapNodeDecoder(decoder *xml.Decoder, startEl xml.StartElement) NodeDecoder { return NodeDecoder{ Decoder: decoder, StartEl: startEl, } } // Token on a Node Decoder returns a xml StartElement. It returns a boolean that indicates the // a token is the node decoder's end node token; and an error which indicates any error // that occurred while retrieving the start element func (d NodeDecoder) Token() (t xml.StartElement, done bool, err error) { for { token, e := d.Decoder.Token() if e != nil { return t, done, e } // check if we reach end of the node being decoded if el, ok := token.(xml.EndElement); ok { return t, el == d.StartEl.End(), err } if t, ok := token.(xml.StartElement); ok { return restoreAttrNamespaces(t), false, err } // skip token if it is a comment or preamble or empty space value due to indentation // or if it's a value and is not expected } } // restoreAttrNamespaces update XML attributes to restore the short namespaces found within // the raw XML document. func restoreAttrNamespaces(node xml.StartElement) xml.StartElement { if len(node.Attr) == 0 { return node } // Generate a mapping of XML namespace values to their short names. ns := map[string]string{} for _, a := range node.Attr { if a.Name.Space == "xmlns" { ns[a.Value] = a.Name.Local break } } for i, a := range node.Attr { if a.Name.Space == "xmlns" { continue } // By default, xml.Decoder will fully resolve these namespaces. So if you had // then by default the second attribute would have the `Name.Space` resolved to `baz`. But we need it to // continue to resolve as `bar` so we can easily identify it later on. if v, ok := ns[node.Attr[i].Name.Space]; ok { node.Attr[i].Name.Space = v } } return node } // GetElement looks for the given tag name at the current level, and returns the element if found, and // skipping over non-matching elements. Returns an error if the node is not found, or if an error occurs while walking // the document. func (d NodeDecoder) GetElement(name string) (t xml.StartElement, err error) { for { token, done, err := d.Token() if err != nil { return t, err } if done { return t, fmt.Errorf("%s node not found", name) } switch { case strings.EqualFold(name, token.Name.Local): return token, nil default: err = d.Decoder.Skip() if err != nil { return t, err } } } } // Value provides an abstraction to retrieve char data value within an xml element. // The method will return an error if it encounters a nested xml element instead of char data. // This method should only be used to retrieve simple type or blob shape values as []byte. func (d NodeDecoder) Value() (c []byte, err error) { t, e := d.Decoder.Token() if e != nil { return c, e } endElement := d.StartEl.End() switch ev := t.(type) { case xml.CharData: c = ev.Copy() case xml.EndElement: // end tag or self-closing if ev == endElement { return []byte{}, err } return c, fmt.Errorf("expected value for %v element, got %T type %v instead", d.StartEl.Name.Local, t, t) default: return c, fmt.Errorf("expected value for %v element, got %T type %v instead", d.StartEl.Name.Local, t, t) } t, e = d.Decoder.Token() if e != nil { return c, e } if ev, ok := t.(xml.EndElement); ok { if ev == endElement { return c, err } } return c, fmt.Errorf("expected end element %v, got %T type %v instead", endElement, t, t) } // FetchRootElement takes in a decoder and returns the first start element within the xml body. // This function is useful in fetching the start element of an XML response and ignore the // comments and preamble func FetchRootElement(decoder *xml.Decoder) (startElement xml.StartElement, err error) { for { t, e := decoder.Token() if e != nil { return startElement, e } if startElement, ok := t.(xml.StartElement); ok { return startElement, err } } } smithy-go-1.20.3/encoding/xml/xml_decoder_test.go000066400000000000000000000220121463735525100217660ustar00rootroot00000000000000package xml import ( "bytes" "encoding/xml" "io" "reflect" "strings" "testing" ) func TestXMLNodeDecoder_Token(t *testing.T) { cases := map[string]struct { responseBody io.Reader expectedStartElement xml.StartElement expectedDone bool expectedError string }{ "simple success case": { responseBody: bytes.NewReader([]byte(`abc`)), expectedStartElement: xml.StartElement{ Name: xml.Name{ Local: "", }, }, expectedDone: true, }, "no value": { responseBody: bytes.NewReader([]byte(``)), expectedDone: true, }, "empty body": { responseBody: bytes.NewReader([]byte(``)), expectedError: "EOF", }, "with indentation": { responseBody: bytes.NewReader([]byte(` abc`)), expectedStartElement: xml.StartElement{ Name: xml.Name{ Local: "Struct", }, Attr: []xml.Attr{}, }, }, "with comment and indentation": { responseBody: bytes.NewReader([]byte(` abc`)), expectedStartElement: xml.StartElement{ Name: xml.Name{ Local: "Struct", }, Attr: []xml.Attr{}, }, }, "attr with namespace": { responseBody: bytes.NewReader([]byte(``)), expectedStartElement: xml.StartElement{ Name: xml.Name{ Local: "Grantee", }, Attr: []xml.Attr{ { Name: xml.Name{ Space: "xmlns", Local: "xsi", }, Value: "http://www.w3.org/2001/XMLSchema-instance", }, { Name: xml.Name{ Space: "xsi", Local: "type", }, Value: "CanonicalUser", }, }, }, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { xmlDecoder := xml.NewDecoder(c.responseBody) st, err := FetchRootElement(xmlDecoder) if err != nil { if len(c.expectedError) == 0 { t.Fatalf("Expected no error, got %v", err) } if e, a := c.expectedError, err; !strings.Contains(err.Error(), c.expectedError) { t.Fatalf("expected error to contain %v, found %v", e, a.Error()) } } nodeDecoder := WrapNodeDecoder(xmlDecoder, st) token, done, err := nodeDecoder.Token() if err != nil { if len(c.expectedError) == 0 { t.Fatalf("Expected no error, got %v", err) } if e, a := c.expectedError, err; !strings.Contains(err.Error(), c.expectedError) { t.Fatalf("expected error to contain %v, found %v", e, a.Error()) } } if e, a := c.expectedDone, done; e != a { t.Fatalf("expected a valid end element token for the xml document, got none") } if !reflect.DeepEqual(c.expectedStartElement, token) { t.Fatalf("Found diff : %v != %v", c.expectedStartElement, token) } }) } } func TestXMLNodeDecoder_TokenExample(t *testing.T) { responseBody := bytes.NewReader([]byte(`abc`)) xmlDecoder := xml.NewDecoder(responseBody) // Fetches tag as start element. st, err := FetchRootElement(xmlDecoder) if err != nil { t.Fatalf("Expected no error, got %v", err) } // nodeDecoder will track tag as root node of the document nodeDecoder := WrapNodeDecoder(xmlDecoder, st) // Retrieves tag token, done, err := nodeDecoder.Token() if err != nil { t.Fatalf("Expected no error, got %v", err) } expect := xml.StartElement{Name: xml.Name{Local: "Response"}, Attr: []xml.Attr{}} if !reflect.DeepEqual(expect, token) { t.Fatalf("Found diff : %v != %v", expect, token) } if done { t.Fatalf("expected decoding to not be done yet") } // Skips the value and gets that is the end token of previously retrieved tag. // The way node decoder works it only keeps track of the root start tag using which it was initialized. // Here is used to initialize, while is end element corresponding to already read // tag. We won't be done until we receive token, done, err = nodeDecoder.Token() if err != nil { t.Fatalf("Expected no error, got %v", err) } expect = xml.StartElement{Name: xml.Name{Local: ""}, Attr: nil} if !reflect.DeepEqual(expect, token) { t.Fatalf("Found diff : %v != %v", expect, token) } if done { t.Fatalf("expected decoding to not be done yet") } // Retrieves end element tag corresponding to tag. // Since we got the end element that corresponds to the start element being track, we are done decoding. token, done, err = nodeDecoder.Token() if err != nil { t.Fatalf("Expected no error, got %v", err) } if !reflect.DeepEqual(expect, token) { t.Fatalf("%v != %v", expect, token) } if !done { t.Fatalf("expected decoding to be done as we fetched the end element ") } } func TestXMLNodeDecoder_Value(t *testing.T) { cases := map[string]struct { responseBody io.Reader expectedValue []byte expectedDone bool expectedError string }{ "simple success case": { responseBody: bytes.NewReader([]byte(`abc`)), expectedValue: []byte(`abc`), }, "no value": { responseBody: bytes.NewReader([]byte(``)), expectedValue: []byte{}, }, "self-closing": { responseBody: bytes.NewReader([]byte(``)), expectedValue: []byte{}, }, "empty body": { responseBody: bytes.NewReader([]byte(``)), expectedError: "EOF", }, "start element retrieved": { responseBody: bytes.NewReader([]byte(`abc`)), expectedError: "expected value for Response element, got xml.StartElement type", }, } for name, c := range cases { t.Run(name, func(t *testing.T) { xmlDecoder := xml.NewDecoder(c.responseBody) st, err := FetchRootElement(xmlDecoder) if err != nil { if len(c.expectedError) == 0 { t.Fatalf("Expected no error, got %v", err) } if e, a := c.expectedError, err; !strings.Contains(err.Error(), c.expectedError) { t.Fatalf("expected error to contain %v, found %v", e, a.Error()) } } nodeDecoder := WrapNodeDecoder(xmlDecoder, st) token, err := nodeDecoder.Value() if err != nil { if len(c.expectedError) == 0 { t.Fatalf("Expected no error, got %v", err) } if e, a := c.expectedError, err; !strings.Contains(err.Error(), c.expectedError) { t.Fatalf("expected error to contain %v, found %v", e, a.Error()) } } if !reflect.DeepEqual(c.expectedValue, token) { t.Fatalf("%v != %v", c.expectedValue, token) } }) } } func Test_FetchXMLRootElement(t *testing.T) { cases := map[string]struct { responseBody io.Reader expectedStartElement xml.StartElement expectedError string }{ "simple success case": { responseBody: bytes.NewReader([]byte(`abc`)), expectedStartElement: xml.StartElement{ Name: xml.Name{ Local: "Response", }, Attr: []xml.Attr{}, }, }, "empty body": { responseBody: bytes.NewReader([]byte(``)), expectedError: "EOF", }, "with indentation": { responseBody: bytes.NewReader([]byte(` Sender InvalidGreeting Hi setting foo-id `)), expectedStartElement: xml.StartElement{ Name: xml.Name{ Local: "ErrorResponse", }, Attr: []xml.Attr{}, }, }, "with preamble": { responseBody: bytes.NewReader([]byte(` Sender InvalidGreeting Hi setting foo-id `)), expectedStartElement: xml.StartElement{ Name: xml.Name{ Local: "ErrorResponse", }, Attr: []xml.Attr{}, }, }, "with comments": { responseBody: bytes.NewReader([]byte(` Sender InvalidGreeting Hi setting foo-id `)), expectedStartElement: xml.StartElement{ Name: xml.Name{ Local: "ErrorResponse", }, Attr: []xml.Attr{}, }, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { decoder := xml.NewDecoder(c.responseBody) st, err := FetchRootElement(decoder) if err != nil { if len(c.expectedError) == 0 { t.Fatalf("Expected no error, got %v", err) } if e, a := c.expectedError, err; !strings.Contains(err.Error(), c.expectedError) { t.Fatalf("expected error to contain %v, found %v", e, a.Error()) } } if !reflect.DeepEqual(c.expectedStartElement, st) { t.Fatalf("Found diff : %v != %v", c.expectedStartElement, st) } }) } } smithy-go-1.20.3/endpoints/000077500000000000000000000000001463735525100155335ustar00rootroot00000000000000smithy-go-1.20.3/endpoints/endpoint.go000066400000000000000000000011061463735525100177000ustar00rootroot00000000000000package transport import ( "net/http" "net/url" "github.com/aws/smithy-go" ) // Endpoint is the endpoint object returned by Endpoint resolution V2 type Endpoint struct { // The complete URL minimally specfiying the scheme and host. // May optionally specify the port and base path component. URI url.URL // An optional set of headers to be sent using transport layer headers. Headers http.Header // A grab-bag property map of endpoint attributes. The // values present here are subject to change, or being add/removed at any // time. Properties smithy.Properties } smithy-go-1.20.3/endpoints/private/000077500000000000000000000000001463735525100172055ustar00rootroot00000000000000smithy-go-1.20.3/endpoints/private/README.md000066400000000000000000000003471463735525100204700ustar00rootroot00000000000000## Smithy Go Private packages ## `private` is a collection of packages used internally by Smithy Go, and is subject to have breaking changes. This package is not `internal` because it is an implementation detail of generated code. smithy-go-1.20.3/endpoints/private/rulesfn/000077500000000000000000000000001463735525100206635ustar00rootroot00000000000000smithy-go-1.20.3/endpoints/private/rulesfn/doc.go000066400000000000000000000001621463735525100217560ustar00rootroot00000000000000// Package rulesfn provides endpoint rule functions for evaluating endpoint // resolution rules. package rulesfn smithy-go-1.20.3/endpoints/private/rulesfn/strings.go000066400000000000000000000011411463735525100227000ustar00rootroot00000000000000package rulesfn // Substring returns the substring of the input provided. If the start or stop // indexes are not valid for the input nil will be returned. If errors occur // they will be added to the provided [ErrorCollector]. func SubString(input string, start, stop int, reverse bool) *string { if start < 0 || stop < 1 || start >= stop || len(input) < stop { return nil } for _, r := range input { if r > 127 { return nil } } if !reverse { v := input[start:stop] return &v } rStart := len(input) - stop rStop := len(input) - start return SubString(input, rStart, rStop, false) } smithy-go-1.20.3/endpoints/private/rulesfn/strings_test.go000066400000000000000000000025041463735525100237430ustar00rootroot00000000000000package rulesfn import ( "testing" "github.com/aws/smithy-go/ptr" ) func TestSubString(t *testing.T) { cases := map[string]struct { input string start, stop int reverse bool expect *string }{ "prefix": { input: "abcde", start: 0, stop: 3, reverse: false, expect: ptr.String("abc"), }, "prefix max-ascii": { input: "abcde\u007F", start: 0, stop: 3, reverse: false, expect: ptr.String("abc"), }, "suffix reverse": { input: "abcde", start: 0, stop: 3, reverse: true, expect: ptr.String("cde"), }, "too long": { input: "ab", start: 0, stop: 3, reverse: false, expect: nil, }, "invalid start index": { input: "ab", start: -1, stop: 3, reverse: false, expect: nil, }, "invalid stop index": { input: "ab", start: 0, stop: 0, reverse: false, expect: nil, }, "non-ascii": { input: "abðŸ±", start: 0, stop: 1, reverse: false, expect: nil, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { actual := SubString(c.input, c.start, c.stop, c.reverse) if c.expect == nil { if actual != nil { t.Fatalf("expect no result, got %v", *actual) } return } if actual == nil { t.Fatalf("expect result, got none") } if e, a := *c.expect, *actual; e != a { t.Errorf("expect %q, got %q", e, a) } }) } } smithy-go-1.20.3/endpoints/private/rulesfn/uri.go000066400000000000000000000072771463735525100220260ustar00rootroot00000000000000package rulesfn import ( "fmt" "net" "net/url" "strings" smithyhttp "github.com/aws/smithy-go/transport/http" ) // IsValidHostLabel returns if the input is a single valid [RFC 1123] host // label. If allowSubDomains is true, will allow validation to include nested // host labels. Returns false if the input is not a valid host label. If errors // occur they will be added to the provided [ErrorCollector]. // // [RFC 1123]: https://www.ietf.org/rfc/rfc1123.txt func IsValidHostLabel(input string, allowSubDomains bool) bool { var labels []string if allowSubDomains { labels = strings.Split(input, ".") } else { labels = []string{input} } for _, label := range labels { if !smithyhttp.ValidHostLabel(label) { return false } } return true } // ParseURL returns a [URL] if the provided string could be parsed. Returns nil // if the string could not be parsed. Any parsing error will be added to the // [ErrorCollector]. // // If the input URL string contains an IP6 address with a zone index. The // returned [builtin.URL.Authority] value will contain the percent escaped (%) // zone index separator. func ParseURL(input string) *URL { u, err := url.Parse(input) if err != nil { return nil } if u.RawQuery != "" { return nil } if u.Scheme != "http" && u.Scheme != "https" { return nil } normalizedPath := u.Path if !strings.HasPrefix(normalizedPath, "/") { normalizedPath = "/" + normalizedPath } if !strings.HasSuffix(normalizedPath, "/") { normalizedPath = normalizedPath + "/" } // IP6 hosts may have zone indexes that need to be escaped to be valid in a // URI. The Go URL parser will unescape the `%25` into `%`. This needs to // be reverted since the returned URL will be used in string builders. authority := strings.ReplaceAll(u.Host, "%", "%25") return &URL{ Scheme: u.Scheme, Authority: authority, Path: u.Path, NormalizedPath: normalizedPath, IsIp: net.ParseIP(hostnameWithoutZone(u)) != nil, } } // URL provides the structure describing the parts of a parsed URL returned by // [ParseURL]. type URL struct { Scheme string // https://www.rfc-editor.org/rfc/rfc3986#section-3.1 Authority string // https://www.rfc-editor.org/rfc/rfc3986#section-3.2 Path string // https://www.rfc-editor.org/rfc/rfc3986#section-3.3 NormalizedPath string // https://www.rfc-editor.org/rfc/rfc3986#section-6.2.3 IsIp bool } // URIEncode returns an percent-encoded [RFC3986 section 2.1] version of the // input string. // // [RFC3986 section 2.1]: https://www.rfc-editor.org/rfc/rfc3986#section-2.1 func URIEncode(input string) string { var output strings.Builder for _, c := range []byte(input) { if validPercentEncodedChar(c) { output.WriteByte(c) continue } fmt.Fprintf(&output, "%%%X", c) } return output.String() } func validPercentEncodedChar(c byte) bool { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~' } // hostname implements u.Hostname() but strips the ipv6 zone ID (if present) // such that net.ParseIP can still recognize IPv6 addresses with zone IDs. // // FUTURE(10/2023): netip.ParseAddr handles this natively but we can't take // that package as a dependency yet due to our min go version (1.15, netip // starts in 1.18). When we align with go runtime deprecation policy in // 10/2023, we can remove this. func hostnameWithoutZone(u *url.URL) string { full := u.Hostname() // this more or less mimics the internals of net/ (see unexported // splitHostZone in that source) but throws the zone away because we don't // need it if i := strings.LastIndex(full, "%"); i > -1 { return full[:i] } return full } smithy-go-1.20.3/endpoints/private/rulesfn/uri_test.go000066400000000000000000000106071463735525100230540ustar00rootroot00000000000000package rulesfn import ( "testing" ) func TestURIEncode(t *testing.T) { cases := map[string]struct { input string expect string }{ "no encoding": { input: "a-zA-Z0-9-_.~", expect: "a-zA-Z0-9-_.~", }, "with encoding": { input: "🛠becomes 🦋", expect: "%F0%9F%90%9B%20becomes%20%F0%9F%A6%8B", }, } for name, c := range cases { t.Run(name, func(t *testing.T) { actual := URIEncode(c.input) if e, a := c.expect, actual; e != a { t.Errorf("expect `%v` encoding, got `%v`", e, a) } }) } } func TestParseURL(t *testing.T) { cases := map[string]struct { input string expect *URL }{ "https hostname with no path": { input: "https://example.com", expect: &URL{ Scheme: "https", Authority: "example.com", Path: "", NormalizedPath: "/", }, }, "http hostname with no path": { input: "http://example.com", expect: &URL{ Scheme: "http", Authority: "example.com", Path: "", NormalizedPath: "/", }, }, "https hostname with port with path": { input: "https://example.com:80/foo/bar", expect: &URL{ Scheme: "https", Authority: "example.com:80", Path: "/foo/bar", NormalizedPath: "/foo/bar/", }, }, "invalid port": { input: "https://example.com:abc", expect: nil, }, "with query": { input: "https://example.com:8443?foo=bar&faz=baz", expect: nil, }, "ip4 URL": { input: "https://127.0.0.1", expect: &URL{ Scheme: "https", Authority: "127.0.0.1", Path: "", NormalizedPath: "/", IsIp: true, }, }, "ip4 URL with port": { input: "https://127.0.0.1:8443", expect: &URL{ Scheme: "https", Authority: "127.0.0.1:8443", Path: "", NormalizedPath: "/", IsIp: true, }, }, "ip6 short": { input: "https://[fe80::1]", expect: &URL{ Scheme: "https", Authority: "[fe80::1]", Path: "", NormalizedPath: "/", IsIp: true, }, }, "ip6 short with interface": { input: "https://[fe80::1%25en0]", expect: &URL{ Scheme: "https", Authority: "[fe80::1%25en0]", Path: "", NormalizedPath: "/", IsIp: true, }, }, "ip6 short with port": { input: "https://[fe80::1]:8443", expect: &URL{ Scheme: "https", Authority: "[fe80::1]:8443", Path: "", NormalizedPath: "/", IsIp: true, }, }, "ip6 short with port with interface": { input: "https://[fe80::1%25en0]:8443", expect: &URL{ Scheme: "https", Authority: "[fe80::1%25en0]:8443", Path: "", NormalizedPath: "/", IsIp: true, }, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { actual := ParseURL(c.input) if c.expect == nil { if actual != nil { t.Fatalf("expect no result, got %v", *actual) } return } if actual == nil { t.Fatalf("expect result, got none") } if *c.expect != *actual { t.Errorf("%v != %v", *c.expect, *actual) } }) } } func TestIsValidHostLabel(t *testing.T) { cases := map[string]struct { input string allowSubDomains bool expect bool }{ "single label no split": { input: "abc123-", expect: true, }, "single label with split": { input: "abc123-", allowSubDomains: true, expect: true, }, "multiple labels no split": { input: "abc.123-", expect: false, }, "multiple labels with split": { input: "abc.123-", allowSubDomains: true, expect: true, }, "multiple labels with split invalid label": { input: "abc.123-...", allowSubDomains: true, expect: false, }, "max length host label": { input: "012345678901234567890123456789012345678901234567890123456789123", expect: true, }, "too large host label": { input: "0123456789012345678901234567890123456789012345678901234567891234", expect: false, }, "too small host label": { input: "", expect: false, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { actual := IsValidHostLabel(c.input, c.allowSubDomains) if e, a := c.expect, actual; e != a { t.Fatalf("expect %v valid host label, got %v", e, a) } }) } } smithy-go-1.20.3/errors.go000066400000000000000000000075141463735525100154020ustar00rootroot00000000000000package smithy import "fmt" // APIError provides the generic API and protocol agnostic error type all SDK // generated exception types will implement. type APIError interface { error // ErrorCode returns the error code for the API exception. ErrorCode() string // ErrorMessage returns the error message for the API exception. ErrorMessage() string // ErrorFault returns the fault for the API exception. ErrorFault() ErrorFault } // GenericAPIError provides a generic concrete API error type that SDKs can use // to deserialize error responses into. Should be used for unmodeled or untyped // errors. type GenericAPIError struct { Code string Message string Fault ErrorFault } // ErrorCode returns the error code for the API exception. func (e *GenericAPIError) ErrorCode() string { return e.Code } // ErrorMessage returns the error message for the API exception. func (e *GenericAPIError) ErrorMessage() string { return e.Message } // ErrorFault returns the fault for the API exception. func (e *GenericAPIError) ErrorFault() ErrorFault { return e.Fault } func (e *GenericAPIError) Error() string { return fmt.Sprintf("api error %s: %s", e.Code, e.Message) } var _ APIError = (*GenericAPIError)(nil) // OperationError decorates an underlying error which occurred while invoking // an operation with names of the operation and API. type OperationError struct { ServiceID string OperationName string Err error } // Service returns the name of the API service the error occurred with. func (e *OperationError) Service() string { return e.ServiceID } // Operation returns the name of the API operation the error occurred with. func (e *OperationError) Operation() string { return e.OperationName } // Unwrap returns the nested error if any, or nil. func (e *OperationError) Unwrap() error { return e.Err } func (e *OperationError) Error() string { return fmt.Sprintf("operation error %s: %s, %v", e.ServiceID, e.OperationName, e.Err) } // DeserializationError provides a wrapper for an error that occurs during // deserialization. type DeserializationError struct { Err error // original error Snapshot []byte } // Error returns a formatted error for DeserializationError func (e *DeserializationError) Error() string { const msg = "deserialization failed" if e.Err == nil { return msg } return fmt.Sprintf("%s, %v", msg, e.Err) } // Unwrap returns the underlying Error in DeserializationError func (e *DeserializationError) Unwrap() error { return e.Err } // ErrorFault provides the type for a Smithy API error fault. type ErrorFault int // ErrorFault enumeration values const ( FaultUnknown ErrorFault = iota FaultServer FaultClient ) func (f ErrorFault) String() string { switch f { case FaultServer: return "server" case FaultClient: return "client" default: return "unknown" } } // SerializationError represents an error that occurred while attempting to serialize a request type SerializationError struct { Err error // original error } // Error returns a formatted error for SerializationError func (e *SerializationError) Error() string { const msg = "serialization failed" if e.Err == nil { return msg } return fmt.Sprintf("%s: %v", msg, e.Err) } // Unwrap returns the underlying Error in SerializationError func (e *SerializationError) Unwrap() error { return e.Err } // CanceledError is the error that will be returned by an API request that was // canceled. API operations given a Context may return this error when // canceled. type CanceledError struct { Err error } // CanceledError returns true to satisfy interfaces checking for canceled errors. func (*CanceledError) CanceledError() bool { return true } // Unwrap returns the underlying error, if there was one. func (e *CanceledError) Unwrap() error { return e.Err } func (e *CanceledError) Error() string { return fmt.Sprintf("canceled, %v", e.Err) } smithy-go-1.20.3/go.mod000066400000000000000000000000511463735525100146320ustar00rootroot00000000000000module github.com/aws/smithy-go go 1.20 smithy-go-1.20.3/go.sum000066400000000000000000000000001463735525100146510ustar00rootroot00000000000000smithy-go-1.20.3/go_module_metadata.go000066400000000000000000000002651463735525100176740ustar00rootroot00000000000000// Code generated by internal/repotools/cmd/updatemodulemeta DO NOT EDIT. package smithy // goModuleVersion is the tagged release for this module const goModuleVersion = "1.20.3" smithy-go-1.20.3/internal/000077500000000000000000000000001463735525100153445ustar00rootroot00000000000000smithy-go-1.20.3/internal/sync/000077500000000000000000000000001463735525100163205ustar00rootroot00000000000000smithy-go-1.20.3/internal/sync/singleflight/000077500000000000000000000000001463735525100207775ustar00rootroot00000000000000smithy-go-1.20.3/internal/sync/singleflight/LICENSE000066400000000000000000000027071463735525100220120ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. smithy-go-1.20.3/internal/sync/singleflight/docs.go000066400000000000000000000006061463735525100222600ustar00rootroot00000000000000// Package singleflight provides a duplicate function call suppression // mechanism. This package is a fork of the Go golang.org/x/sync/singleflight // package. The package is forked, because the package a part of the unstable // and unversioned golang.org/x/sync module. // // https://github.com/golang/sync/tree/67f06af15bc961c363a7260195bcd53487529a21/singleflight package singleflight smithy-go-1.20.3/internal/sync/singleflight/singleflight.go000066400000000000000000000124541463735525100240130ustar00rootroot00000000000000// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package singleflight import ( "bytes" "errors" "fmt" "runtime" "runtime/debug" "sync" ) // errGoexit indicates the runtime.Goexit was called in // the user given function. var errGoexit = errors.New("runtime.Goexit was called") // A panicError is an arbitrary value recovered from a panic // with the stack trace during the execution of given function. type panicError struct { value interface{} stack []byte } // Error implements error interface. func (p *panicError) Error() string { return fmt.Sprintf("%v\n\n%s", p.value, p.stack) } func newPanicError(v interface{}) error { stack := debug.Stack() // The first line of the stack trace is of the form "goroutine N [status]:" // but by the time the panic reaches Do the goroutine may no longer exist // and its status will have changed. Trim out the misleading line. if line := bytes.IndexByte(stack[:], '\n'); line >= 0 { stack = stack[line+1:] } return &panicError{value: v, stack: stack} } // call is an in-flight or completed singleflight.Do call type call struct { wg sync.WaitGroup // These fields are written once before the WaitGroup is done // and are only read after the WaitGroup is done. val interface{} err error // forgotten indicates whether Forget was called with this call's key // while the call was still in flight. forgotten bool // These fields are read and written with the singleflight // mutex held before the WaitGroup is done, and are read but // not written after the WaitGroup is done. dups int chans []chan<- Result } // Group represents a class of work and forms a namespace in // which units of work can be executed with duplicate suppression. type Group struct { mu sync.Mutex // protects m m map[string]*call // lazily initialized } // Result holds the results of Do, so they can be passed // on a channel. type Result struct { Val interface{} Err error Shared bool } // Do executes and returns the results of the given function, making // sure that only one execution is in-flight for a given key at a // time. If a duplicate comes in, the duplicate caller waits for the // original to complete and receives the same results. // The return value shared indicates whether v was given to multiple callers. func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) { g.mu.Lock() if g.m == nil { g.m = make(map[string]*call) } if c, ok := g.m[key]; ok { c.dups++ g.mu.Unlock() c.wg.Wait() if e, ok := c.err.(*panicError); ok { panic(e) } else if c.err == errGoexit { runtime.Goexit() } return c.val, c.err, true } c := new(call) c.wg.Add(1) g.m[key] = c g.mu.Unlock() g.doCall(c, key, fn) return c.val, c.err, c.dups > 0 } // DoChan is like Do but returns a channel that will receive the // results when they are ready. // // The returned channel will not be closed. func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result { ch := make(chan Result, 1) g.mu.Lock() if g.m == nil { g.m = make(map[string]*call) } if c, ok := g.m[key]; ok { c.dups++ c.chans = append(c.chans, ch) g.mu.Unlock() return ch } c := &call{chans: []chan<- Result{ch}} c.wg.Add(1) g.m[key] = c g.mu.Unlock() go g.doCall(c, key, fn) return ch } // doCall handles the single call for a key. func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) { normalReturn := false recovered := false // use double-defer to distinguish panic from runtime.Goexit, // more details see https://golang.org/cl/134395 defer func() { // the given function invoked runtime.Goexit if !normalReturn && !recovered { c.err = errGoexit } c.wg.Done() g.mu.Lock() defer g.mu.Unlock() if !c.forgotten { delete(g.m, key) } if e, ok := c.err.(*panicError); ok { // In order to prevent the waiting channels from being blocked forever, // needs to ensure that this panic cannot be recovered. if len(c.chans) > 0 { go panic(e) select {} // Keep this goroutine around so that it will appear in the crash dump. } else { panic(e) } } else if c.err == errGoexit { // Already in the process of goexit, no need to call again } else { // Normal return for _, ch := range c.chans { ch <- Result{c.val, c.err, c.dups > 0} } } }() func() { defer func() { if !normalReturn { // Ideally, we would wait to take a stack trace until we've determined // whether this is a panic or a runtime.Goexit. // // Unfortunately, the only way we can distinguish the two is to see // whether the recover stopped the goroutine from terminating, and by // the time we know that, the part of the stack trace relevant to the // panic has been discarded. if r := recover(); r != nil { c.err = newPanicError(r) } } }() c.val, c.err = fn() normalReturn = true }() if !normalReturn { recovered = true } } // Forget tells the singleflight to forget about a key. Future calls // to Do for this key will call the function rather than waiting for // an earlier call to complete. func (g *Group) Forget(key string) { g.mu.Lock() if c, ok := g.m[key]; ok { c.forgotten = true } delete(g.m, key) g.mu.Unlock() } smithy-go-1.20.3/internal/sync/singleflight/singleflight_test.go000066400000000000000000000154201463735525100250460ustar00rootroot00000000000000// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package singleflight import ( "bytes" "errors" "fmt" "os" "os/exec" "runtime" "runtime/debug" "strings" "sync" "sync/atomic" "testing" "time" ) func TestDo(t *testing.T) { var g Group v, err, _ := g.Do("key", func() (interface{}, error) { return "bar", nil }) if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want { t.Errorf("Do = %v; want %v", got, want) } if err != nil { t.Errorf("Do error = %v", err) } } func TestDoErr(t *testing.T) { var g Group someErr := errors.New("Some error") v, err, _ := g.Do("key", func() (interface{}, error) { return nil, someErr }) if err != someErr { t.Errorf("Do error = %v; want someErr %v", err, someErr) } if v != nil { t.Errorf("unexpected non-nil value %#v", v) } } func TestDoDupSuppress(t *testing.T) { var g Group var wg1, wg2 sync.WaitGroup c := make(chan string, 1) var calls int32 fn := func() (interface{}, error) { if atomic.AddInt32(&calls, 1) == 1 { // First invocation. wg1.Done() } v := <-c c <- v // pump; make available for any future calls time.Sleep(10 * time.Millisecond) // let more goroutines enter Do return v, nil } const n = 10 wg1.Add(1) for i := 0; i < n; i++ { wg1.Add(1) wg2.Add(1) go func() { defer wg2.Done() wg1.Done() v, err, _ := g.Do("key", fn) if err != nil { t.Errorf("Do error: %v", err) return } if s, _ := v.(string); s != "bar" { t.Errorf("Do = %T %v; want %q", v, v, "bar") } }() } wg1.Wait() // At least one goroutine is in fn now and all of them have at // least reached the line before the Do. c <- "bar" wg2.Wait() if got := atomic.LoadInt32(&calls); got <= 0 || got >= n { t.Errorf("number of calls = %d; want over 0 and less than %d", got, n) } } // Test that singleflight behaves correctly after Forget called. // See https://github.com/golang/go/issues/31420 func TestForget(t *testing.T) { var g Group var ( firstStarted = make(chan struct{}) unblockFirst = make(chan struct{}) firstFinished = make(chan struct{}) ) go func() { g.Do("key", func() (i interface{}, e error) { close(firstStarted) <-unblockFirst close(firstFinished) return }) }() <-firstStarted g.Forget("key") unblockSecond := make(chan struct{}) secondResult := g.DoChan("key", func() (i interface{}, e error) { <-unblockSecond return 2, nil }) close(unblockFirst) <-firstFinished thirdResult := g.DoChan("key", func() (i interface{}, e error) { return 3, nil }) close(unblockSecond) <-secondResult r := <-thirdResult if r.Val != 2 { t.Errorf("We should receive result produced by second call, expected: 2, got %d", r.Val) } } func TestDoChan(t *testing.T) { var g Group ch := g.DoChan("key", func() (interface{}, error) { return "bar", nil }) res := <-ch v := res.Val err := res.Err if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want { t.Errorf("Do = %v; want %v", got, want) } if err != nil { t.Errorf("Do error = %v", err) } } // Test singleflight behaves correctly after Do panic. // See https://github.com/golang/go/issues/41133 func TestPanicDo(t *testing.T) { var g Group fn := func() (interface{}, error) { panic("invalid memory address or nil pointer dereference") } const n = 5 waited := int32(n) panicCount := int32(0) done := make(chan struct{}) for i := 0; i < n; i++ { go func() { defer func() { if err := recover(); err != nil { t.Logf("Got panic: %v\n%s", err, debug.Stack()) atomic.AddInt32(&panicCount, 1) } if atomic.AddInt32(&waited, -1) == 0 { close(done) } }() g.Do("key", fn) }() } select { case <-done: if panicCount != n { t.Errorf("Expect %d panic, but got %d", n, panicCount) } case <-time.After(time.Second): t.Fatalf("Do hangs") } } func TestGoexitDo(t *testing.T) { var g Group fn := func() (interface{}, error) { runtime.Goexit() return nil, nil } const n = 5 waited := int32(n) done := make(chan struct{}) for i := 0; i < n; i++ { go func() { var err error defer func() { if err != nil { t.Errorf("Error should be nil, but got: %v", err) } if atomic.AddInt32(&waited, -1) == 0 { close(done) } }() _, err, _ = g.Do("key", fn) }() } select { case <-done: case <-time.After(time.Second): t.Fatalf("Do hangs") } } func TestPanicDoChan(t *testing.T) { if runtime.GOOS == "js" { t.Skipf("js does not support exec") } if os.Getenv("TEST_PANIC_DOCHAN") != "" { defer func() { recover() }() g := new(Group) ch := g.DoChan("", func() (interface{}, error) { panic("Panicking in DoChan") }) <-ch t.Fatalf("DoChan unexpectedly returned") } t.Parallel() cmd := exec.Command(os.Args[0], "-test.run="+t.Name(), "-test.v") cmd.Env = append(os.Environ(), "TEST_PANIC_DOCHAN=1") out := new(bytes.Buffer) cmd.Stdout = out cmd.Stderr = out if err := cmd.Start(); err != nil { t.Fatal(err) } err := cmd.Wait() t.Logf("%s:\n%s", strings.Join(cmd.Args, " "), out) if err == nil { t.Errorf("Test subprocess passed; want a crash due to panic in DoChan") } if bytes.Contains(out.Bytes(), []byte("DoChan unexpectedly")) { t.Errorf("Test subprocess failed with an unexpected failure mode.") } if !bytes.Contains(out.Bytes(), []byte("Panicking in DoChan")) { t.Errorf("Test subprocess failed, but the crash isn't caused by panicking in DoChan") } } func TestPanicDoSharedByDoChan(t *testing.T) { if runtime.GOOS == "js" { t.Skipf("js does not support exec") } if os.Getenv("TEST_PANIC_DOCHAN") != "" { blocked := make(chan struct{}) unblock := make(chan struct{}) g := new(Group) go func() { defer func() { recover() }() g.Do("", func() (interface{}, error) { close(blocked) <-unblock panic("Panicking in Do") }) }() <-blocked ch := g.DoChan("", func() (interface{}, error) { panic("DoChan unexpectedly executed callback") }) close(unblock) <-ch t.Fatalf("DoChan unexpectedly returned") } t.Parallel() cmd := exec.Command(os.Args[0], "-test.run="+t.Name(), "-test.v") cmd.Env = append(os.Environ(), "TEST_PANIC_DOCHAN=1") out := new(bytes.Buffer) cmd.Stdout = out cmd.Stderr = out if err := cmd.Start(); err != nil { t.Fatal(err) } err := cmd.Wait() t.Logf("%s:\n%s", strings.Join(cmd.Args, " "), out) if err == nil { t.Errorf("Test subprocess passed; want a crash due to panic in Do shared by DoChan") } if bytes.Contains(out.Bytes(), []byte("DoChan unexpectedly")) { t.Errorf("Test subprocess failed with an unexpected failure mode.") } if !bytes.Contains(out.Bytes(), []byte("Panicking in Do")) { t.Errorf("Test subprocess failed, but the crash isn't caused by panicking in Do") } } smithy-go-1.20.3/io/000077500000000000000000000000001463735525100141375ustar00rootroot00000000000000smithy-go-1.20.3/io/byte.go000066400000000000000000000003501463735525100154270ustar00rootroot00000000000000package io const ( // Byte is 8 bits Byte int64 = 1 // KibiByte (KiB) is 1024 Bytes KibiByte = Byte * 1024 // MebiByte (MiB) is 1024 KiB MebiByte = KibiByte * 1024 // GibiByte (GiB) is 1024 MiB GibiByte = MebiByte * 1024 ) smithy-go-1.20.3/io/doc.go000066400000000000000000000001161463735525100152310ustar00rootroot00000000000000// Package io provides utilities for Smithy generated API clients. package io smithy-go-1.20.3/io/reader.go000066400000000000000000000004001463735525100157220ustar00rootroot00000000000000package io import ( "io" ) // ReadSeekNopCloser wraps an io.ReadSeeker with an additional Close method // that does nothing. type ReadSeekNopCloser struct { io.ReadSeeker } // Close does nothing. func (ReadSeekNopCloser) Close() error { return nil } smithy-go-1.20.3/io/ringbuffer.go000066400000000000000000000041141463735525100166170ustar00rootroot00000000000000package io import ( "bytes" "io" ) // RingBuffer struct satisfies io.ReadWrite interface. // // ReadBuffer is a revolving buffer data structure, which can be used to store snapshots of data in a // revolving window. type RingBuffer struct { slice []byte start int end int size int } // NewRingBuffer method takes in a byte slice as an input and returns a RingBuffer. func NewRingBuffer(slice []byte) *RingBuffer { ringBuf := RingBuffer{ slice: slice, } return &ringBuf } // Write method inserts the elements in a byte slice, and returns the number of bytes written along with any error. func (r *RingBuffer) Write(p []byte) (int, error) { for _, b := range p { // check if end points to invalid index, we need to circle back if r.end == len(r.slice) { r.end = 0 } // check if start points to invalid index, we need to circle back if r.start == len(r.slice) { r.start = 0 } // if ring buffer is filled, increment the start index if r.size == len(r.slice) { r.size-- r.start++ } r.slice[r.end] = b r.end++ r.size++ } return len(p), nil } // Read copies the data on the ring buffer into the byte slice provided to the method. // Returns the read count along with any error encountered while reading. func (r *RingBuffer) Read(p []byte) (int, error) { // readCount keeps track of the number of bytes read var readCount int for j := 0; j < len(p); j++ { // if ring buffer is empty or completely read // return EOF error. if r.size == 0 { return readCount, io.EOF } if r.start == len(r.slice) { r.start = 0 } p[j] = r.slice[r.start] readCount++ // increment the start pointer for ring buffer r.start++ // decrement the size of ring buffer r.size-- } return readCount, nil } // Len returns the number of unread bytes in the buffer. func (r *RingBuffer) Len() int { return r.size } // Bytes returns a copy of the RingBuffer's bytes. func (r RingBuffer) Bytes() []byte { var b bytes.Buffer io.Copy(&b, &r) return b.Bytes() } // Reset resets the ring buffer. func (r *RingBuffer) Reset() { *r = RingBuffer{ slice: r.slice, } } smithy-go-1.20.3/io/ringbuffer_test.go000066400000000000000000000353311463735525100176630ustar00rootroot00000000000000package io import ( "bytes" "io" "io/ioutil" "strconv" "strings" "testing" ) func TestRingBuffer_Write(t *testing.T) { cases := map[string]struct { sliceCapacity int input []byte expectedStart int expectedEnd int expectedSize int expectedWrittenBuffer []byte }{ "RingBuffer capacity matches Bytes written": { sliceCapacity: 11, input: []byte("hello world"), expectedStart: 0, expectedEnd: 11, expectedSize: 11, expectedWrittenBuffer: []byte("hello world"), }, "RingBuffer capacity is lower than Bytes written": { sliceCapacity: 10, input: []byte("hello world"), expectedStart: 1, expectedEnd: 1, expectedSize: 10, expectedWrittenBuffer: []byte("dello worl"), }, "RingBuffer capacity is more than Bytes written": { sliceCapacity: 12, input: []byte("hello world"), expectedStart: 0, expectedEnd: 11, expectedSize: 11, expectedWrittenBuffer: []byte("hello world"), }, "No Bytes written": { sliceCapacity: 10, input: []byte(""), expectedStart: 0, expectedEnd: 0, expectedSize: 0, expectedWrittenBuffer: []byte(""), }, } for name, c := range cases { t.Run(name, func(t *testing.T) { byteSlice := make([]byte, c.sliceCapacity) ringBuffer := NewRingBuffer(byteSlice) ringBuffer.Write(c.input) if e, a := c.expectedSize, ringBuffer.size; e != a { t.Errorf("expect default size to be %v , got %v", e, a) } if e, a := c.expectedStart, ringBuffer.start; e != a { t.Errorf("expect deafult start to point to %v , got %v", e, a) } if e, a := c.expectedEnd, ringBuffer.end; e != a { t.Errorf("expect default end to point to %v , got %v", e, a) } if e, a := c.expectedWrittenBuffer, ringBuffer.slice; !bytes.Contains(a, e) { t.Errorf("expect written bytes to be %v , got %v", string(e), string(a)) } }) } } func TestRingBuffer_Read(t *testing.T) { cases := map[string]struct { input []byte numberOfBytesToRead int expectedStartAfterRead int expectedEndAfterRead int expectedSizeOfBufferAfterRead int expectedReadSlice []byte expectedErrorAfterRead error }{ "Read capacity matches Bytes written": { input: []byte("Hello world"), numberOfBytesToRead: 11, expectedStartAfterRead: 11, expectedEndAfterRead: 11, expectedSizeOfBufferAfterRead: 0, expectedReadSlice: []byte("Hello world"), expectedErrorAfterRead: nil, }, "Read capacity is lower than Bytes written": { input: []byte("hello world"), numberOfBytesToRead: 5, expectedStartAfterRead: 5, expectedEndAfterRead: 11, expectedSizeOfBufferAfterRead: 6, expectedReadSlice: []byte("hello"), expectedErrorAfterRead: nil, }, "Read capacity is more than Bytes written": { input: []byte("hello world"), numberOfBytesToRead: 15, expectedStartAfterRead: 11, expectedEndAfterRead: 11, expectedSizeOfBufferAfterRead: 0, expectedReadSlice: []byte("hello world"), expectedErrorAfterRead: io.EOF, }, "No Bytes are read": { input: []byte("hello world"), numberOfBytesToRead: 0, expectedStartAfterRead: 0, expectedEndAfterRead: 11, expectedSizeOfBufferAfterRead: 11, expectedReadSlice: []byte(""), expectedErrorAfterRead: nil, }, "No Bytes written": { input: []byte(""), numberOfBytesToRead: 11, expectedStartAfterRead: 0, expectedEndAfterRead: 0, expectedSizeOfBufferAfterRead: 0, expectedReadSlice: []byte(""), expectedErrorAfterRead: io.EOF, }, "RingBuffer capacity is more than Bytes Written": { input: []byte("h"), numberOfBytesToRead: 11, expectedStartAfterRead: 1, expectedEndAfterRead: 1, expectedSizeOfBufferAfterRead: 0, expectedReadSlice: []byte("h"), expectedErrorAfterRead: io.EOF, }, } for name, c := range cases { byteSlice := make([]byte, 11) t.Run(name, func(t *testing.T) { ringBuffer := NewRingBuffer(byteSlice) readSlice := make([]byte, c.numberOfBytesToRead) ringBuffer.Write(c.input) _, err := ringBuffer.Read(readSlice) if e, a := c.expectedErrorAfterRead, err; e != a { t.Errorf("Expected %v, got %v", e, a) } if e, a := c.expectedReadSlice, readSlice; !bytes.Contains(a, e) { t.Errorf("expect read buffer to be %v, got %v", string(e), string(a)) } if e, a := c.expectedSizeOfBufferAfterRead, ringBuffer.size; e != a { t.Errorf("expect default size to be %v , got %v", e, a) } if e, a := c.expectedStartAfterRead, ringBuffer.start; e != a { t.Errorf("expect default start to point to %v , got %v", e, a) } if e, a := c.expectedEndAfterRead, ringBuffer.end; e != a { t.Errorf("expect default end to point to %v , got %v", e, a) } }) } } func TestRingBuffer_forConsecutiveReadWrites(t *testing.T) { cases := map[string]struct { input []string sliceCapacity int numberOfBytesToRead []int expectedStartAfterRead []int expectedEnd []int expectedSizeOfBufferAfterRead []int expectedReadSlice []string expectedWrittenBuffer []string expectedErrorAfterRead []error }{ "RingBuffer capacity matches Bytes written": { input: []string{"Hello World", "Hello Earth", "Mars,/"}, sliceCapacity: 11, numberOfBytesToRead: []int{5, 11}, expectedStartAfterRead: []int{5, 6}, expectedEnd: []int{11, 6}, expectedSizeOfBufferAfterRead: []int{6, 0}, expectedReadSlice: []string{"Hello", "EarthMars,/"}, expectedWrittenBuffer: []string{"Hello World", "Hello Earth", "Mars,/Earth"}, expectedErrorAfterRead: []error{nil, nil}, }, "RingBuffer capacity is lower than Bytes written": { input: []string{"Hello World", "Hello Earth", "Mars,/"}, sliceCapacity: 5, numberOfBytesToRead: []int{5, 5}, expectedStartAfterRead: []int{1, 3}, expectedEnd: []int{1, 3}, expectedSizeOfBufferAfterRead: []int{0, 0}, expectedReadSlice: []string{"World", "ars,/"}, expectedWrittenBuffer: []string{"dWorl", "thEar", "s,/ar"}, expectedErrorAfterRead: []error{nil, nil}, }, "RingBuffer capacity is more than Bytes written": { input: []string{"Hello World", "Hello Earth", "Mars,/"}, sliceCapacity: 15, numberOfBytesToRead: []int{5, 8}, expectedStartAfterRead: []int{5, 6}, expectedEnd: []int{11, 13}, expectedSizeOfBufferAfterRead: []int{6, 7}, expectedReadSlice: []string{"Hello", "llo Eart"}, expectedWrittenBuffer: []string{"Hello World", "o EarthorldHell", "o EarthMars,/ll"}, expectedErrorAfterRead: []error{nil, nil}, }, "No Bytes written": { input: []string{"", "", ""}, sliceCapacity: 11, numberOfBytesToRead: []int{5, 8}, expectedStartAfterRead: []int{0, 0}, expectedEnd: []int{0, 0}, expectedSizeOfBufferAfterRead: []int{0, 0}, expectedReadSlice: []string{"", ""}, expectedWrittenBuffer: []string{"", "", ""}, expectedErrorAfterRead: []error{io.EOF, io.EOF}, }, } for name, c := range cases { writeSlice := make([]byte, c.sliceCapacity) ringBuffer := NewRingBuffer(writeSlice) t.Run(name, func(t *testing.T) { ringBuffer.Write([]byte(c.input[0])) if e, a := c.expectedWrittenBuffer[0], string(ringBuffer.slice); !strings.Contains(a, e) { t.Errorf("Expected %v, got %v", e, a) } readSlice := make([]byte, c.numberOfBytesToRead[0]) readCount, err := ringBuffer.Read(readSlice) if e, a := c.expectedErrorAfterRead[0], err; e != a { t.Errorf("Expected %v, got %v", e, a) } if e, a := len(c.expectedReadSlice[0]), readCount; e != a { t.Errorf("Expected to read %v bytes, read only %v", e, a) } if e, a := c.expectedReadSlice[0], string(readSlice); !strings.Contains(a, e) { t.Errorf("expect read buffer to be %v, got %v", e, a) } if e, a := c.expectedSizeOfBufferAfterRead[0], ringBuffer.size; e != a { t.Errorf("expect buffer size to be %v , got %v", e, a) } if e, a := c.expectedStartAfterRead[0], ringBuffer.start; e != a { t.Errorf("expect default start to point to %v , got %v", e, a) } if e, a := c.expectedEnd[0], ringBuffer.end; e != a { t.Errorf("expect default end tp point to %v , got %v", e, a) } /* Next cycle of read writes. */ ringBuffer.Write([]byte(c.input[1])) if e, a := c.expectedWrittenBuffer[1], string(ringBuffer.slice); !strings.Contains(a, e) { t.Errorf("Expected %v, got %v", e, a) } ringBuffer.Write([]byte(c.input[2])) if e, a := c.expectedWrittenBuffer[2], string(ringBuffer.slice); !strings.Contains(a, e) { t.Errorf("Expected %v, got %v", e, a) } readSlice = make([]byte, c.numberOfBytesToRead[1]) readCount, err = ringBuffer.Read(readSlice) if e, a := c.expectedErrorAfterRead[1], err; e != a { t.Errorf("Expected %v, got %v", e, a) } if e, a := len(c.expectedReadSlice[1]), readCount; e != a { t.Errorf("Expected to read %v bytes, read only %v", e, a) } if e, a := c.expectedReadSlice[1], string(readSlice); !strings.Contains(a, e) { t.Errorf("expect read buffer to be %v, got %v", e, a) } if e, a := c.expectedSizeOfBufferAfterRead[1], ringBuffer.size; e != a { t.Errorf("expect buffer size to be %v , got %v", e, a) } if e, a := c.expectedStartAfterRead[1], ringBuffer.start; e != a { t.Errorf("expect default start to point to %v , got %v", e, a) } if e, a := c.expectedEnd[1], ringBuffer.end; e != a { t.Errorf("expect default end to point to %v , got %v", e, a) } }) } } func TestRingBuffer_ExhaustiveRead(t *testing.T) { slice := make([]byte, 5) buf := NewRingBuffer(slice) buf.Write([]byte("Hello")) readSlice := make([]byte, 5) readCount, err := buf.Read(readSlice) if e, a := error(nil), err; e != a { t.Errorf("Expected %v, got %v", e, a) } if e, a := 5, readCount; e != a { t.Errorf("Expected to read %v bytes, read only %v", e, a) } if e, a := "Hello", string(readSlice); e != a { t.Errorf("Expected %v to be read, got %v", e, a) } readCount, err = buf.Read(readSlice) if e, a := io.EOF, err; e != a { t.Errorf("Expected %v, got %v", e, a) } if e, a := 0, readCount; e != a { t.Errorf("Expected to read %v bytes, read only %v", e, a) } if e, a := 0, buf.size; e != a { t.Errorf("Expected ring buffer size to be %v, got %v", e, a) } } func TestRingBuffer_Reset(t *testing.T) { byteSlice := make([]byte, 10) ringBuffer := NewRingBuffer(byteSlice) ringBuffer.Write([]byte("Hello-world")) if ringBuffer.size == 0 { t.Errorf("expected ringBuffer to not be empty") } readBuffer := make([]byte, 5) ringBuffer.Read(readBuffer) if ringBuffer.size == 0 { t.Errorf("expected ringBuffer to not be empty") } if e, a := "ello-", string(readBuffer); !strings.EqualFold(e, a) { t.Errorf("expected read string to be %s, got %s", e, a) } // reset the buffer ringBuffer.Reset() if e, a := 0, ringBuffer.size; e != a { t.Errorf("expect default size to be %v , got %v", e, a) } if e, a := 0, ringBuffer.start; e != a { t.Errorf("expect deafult start to point to %v , got %v", e, a) } if e, a := 0, ringBuffer.end; e != a { t.Errorf("expect default end to point to %v , got %v", e, a) } if e, a := 10, len(ringBuffer.slice); e != a { t.Errorf("expect ringBuffer capacity to be %v, got %v", e, a) } ringBuffer.Write([]byte("someThing new")) if ringBuffer.size == 0 { t.Errorf("expected ringBuffer to not be empty") } ringBuffer.Read(readBuffer) if ringBuffer.size == 0 { t.Errorf("expected ringBuffer to not be empty") } // Here the ringBuffer length is 10; while written string is "someThing new"; // The initial characters are thus overwritten by the ringbuffer. // Thus the ring Buffer if completely read will have "eThing new". // Here readBuffer size is 5; thus first 5 character "eThin" is read. if e, a := "eThin", string(readBuffer); !strings.EqualFold(e, a) { t.Errorf("expected read string to be %s, got %s", e, a) } // reset the buffer ringBuffer.Reset() if e, a := 0, ringBuffer.size; e != a { t.Errorf("expect default size to be %v , got %v", e, a) } if e, a := 0, ringBuffer.start; e != a { t.Errorf("expect deafult start to point to %v , got %v", e, a) } if e, a := 0, ringBuffer.end; e != a { t.Errorf("expect default end to point to %v , got %v", e, a) } if e, a := 10, len(ringBuffer.slice); e != a { t.Errorf("expect ringBuffer capacity to be %v, got %v", e, a) } // reading reset ring buffer readCount, _ := ringBuffer.Read(readBuffer) if ringBuffer.size != 0 { t.Errorf("expected ringBuffer to be empty") } if e, a := 0, readCount; e != a { t.Errorf("expected read string to be of length %v, got %v", e, a) } } func TestRingBufferWriteRead(t *testing.T) { cases := []struct { Input []byte BufferSize int Expected []byte }{ { Input: func() []byte { return []byte(`hello world!`) }(), BufferSize: 6, Expected: []byte(`world!`), }, { Input: func() []byte { return []byte(`hello world!`) }(), BufferSize: 12, Expected: []byte(`hello world!`), }, { Input: func() []byte { return []byte(`hello`) }(), BufferSize: 6, Expected: []byte(`hello`), }, { Input: func() []byte { return []byte(`hello!!`) }(), BufferSize: 6, Expected: []byte(`ello!!`), }, } for i, tt := range cases { t.Run(strconv.Itoa(i), func(t *testing.T) { dataReader := bytes.NewReader(tt.Input) ringBuffer := NewRingBuffer(make([]byte, tt.BufferSize)) n, err := io.Copy(ringBuffer, dataReader) if err != nil { t.Errorf("unexpected error, %v", err) return } if e, a := int64(len(tt.Input)), n; e != a { t.Errorf("expect %v, got %v", e, a) } actual, err := ioutil.ReadAll(ringBuffer) if err != nil { t.Errorf("unexpected error, %v", err) return } if string(tt.Expected) != string(actual) { t.Errorf("%v != %v", string(tt.Expected), string(actual)) return } }) } } smithy-go-1.20.3/local-mod-replace.sh000077500000000000000000000014271463735525100173530ustar00rootroot00000000000000#1/usr/bin/env bash PROJECT_DIR="" SMITHY_SOURCE_DIR=$(cd `dirname $0` && pwd) usage() { echo "Usage: $0 [-s SMITHY_SOURCE_DIR] [-d PROJECT_DIR]" 1>&2 exit 1 } while getopts "hs:d:" options; do case "${options}" in s) SMITHY_SOURCE_DIR=${OPTARG} if [ "$SMITHY_SOURCE_DIR" == "" ]; then echo "path to smithy-go source directory is required" || exit usage fi ;; d) PROJECT_DIR=${OPTARG} ;; h) usage ;; *) usage ;; esac done if [ "$PROJECT_DIR" != "" ]; then cd $PROJECT_DIR || exit fi go mod graph | awk '{print $1}' | cut -d '@' -f 1 | sort | uniq | grep "github.com/aws/smithy-go" | while read x; do repPath=${x/github.com\/aws\/smithy-go/${SMITHY_SOURCE_DIR}} echo -replace $x=$repPath done | xargs go mod edit smithy-go-1.20.3/logging/000077500000000000000000000000001463735525100151565ustar00rootroot00000000000000smithy-go-1.20.3/logging/logger.go000066400000000000000000000046371463735525100167760ustar00rootroot00000000000000package logging import ( "context" "io" "log" ) // Classification is the type of the log entry's classification name. type Classification string // Set of standard classifications that can be used by clients and middleware const ( Warn Classification = "WARN" Debug Classification = "DEBUG" ) // Logger is an interface for logging entries at certain classifications. type Logger interface { // Logf is expected to support the standard fmt package "verbs". Logf(classification Classification, format string, v ...interface{}) } // LoggerFunc is a wrapper around a function to satisfy the Logger interface. type LoggerFunc func(classification Classification, format string, v ...interface{}) // Logf delegates the logging request to the wrapped function. func (f LoggerFunc) Logf(classification Classification, format string, v ...interface{}) { f(classification, format, v...) } // ContextLogger is an optional interface a Logger implementation may expose that provides // the ability to create context aware log entries. type ContextLogger interface { WithContext(context.Context) Logger } // WithContext will pass the provided context to logger if it implements the ContextLogger interface and return the resulting // logger. Otherwise the logger will be returned as is. As a special case if a nil logger is provided, a Nop logger will // be returned to the caller. func WithContext(ctx context.Context, logger Logger) Logger { if logger == nil { return Nop{} } cl, ok := logger.(ContextLogger) if !ok { return logger } return cl.WithContext(ctx) } // Nop is a Logger implementation that simply does not perform any logging. type Nop struct{} // Logf simply returns without performing any action func (n Nop) Logf(Classification, string, ...interface{}) { return } // StandardLogger is a Logger implementation that wraps the standard library logger, and delegates logging to it's // Printf method. type StandardLogger struct { Logger *log.Logger } // Logf logs the given classification and message to the underlying logger. func (s StandardLogger) Logf(classification Classification, format string, v ...interface{}) { if len(classification) != 0 { format = string(classification) + " " + format } s.Logger.Printf(format, v...) } // NewStandardLogger returns a new StandardLogger func NewStandardLogger(writer io.Writer) *StandardLogger { return &StandardLogger{ Logger: log.New(writer, "SDK ", log.LstdFlags), } } smithy-go-1.20.3/logging/logger_test.go000066400000000000000000000031341463735525100200240ustar00rootroot00000000000000package logging_test import ( "bytes" "context" "fmt" "regexp" "testing" "github.com/aws/smithy-go/logging" ) func TestNewStandardLogger(t *testing.T) { var buffer bytes.Buffer logger := logging.NewStandardLogger(&buffer) const matchStr = `SDK \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} %s foo bar baz\n` logger.Logf(logging.Debug, "foo %s baz", "bar") match := regexp.MustCompile(fmt.Sprintf(matchStr, "DEBUG")) if !match.Match(buffer.Bytes()) { t.Error("log entry did not match expected") } logger.Logf(logging.Warn, "foo %s baz", "bar") match = regexp.MustCompile(fmt.Sprintf(matchStr, "WARN")) if !match.Match(buffer.Bytes()) { t.Error("log entry did not match expected") } } func TestNop(t *testing.T) { logging.Nop{}.Logf(logging.Debug, "foo") } func TestWithContext(t *testing.T) { l := &mockContextLogger{} expectContextStrValue := "bar" nl := logging.WithContext(context.WithValue(context.Background(), "foo", expectContextStrValue), l) v, ok := nl.(*mockContextLogger) if !ok { t.Fatalf("expect %T, got %T", &mockContextLogger{}, nl) } if v.ctx == nil { t.Fatal("expect context to not be nil") } ctxValue := v.ctx.Value("foo") str, ok := ctxValue.(string) if !ok { t.Fatalf("expect string, got %T", str) } if str != expectContextStrValue { t.Errorf("expect %v, got %v", expectContextStrValue, str) } } type mockContextLogger struct { ctx context.Context } func (m mockContextLogger) WithContext(ctx context.Context) logging.Logger { m.ctx = ctx return &m } func (m mockContextLogger) Logf(level logging.Classification, format string, v ...interface{}) { return } smithy-go-1.20.3/middleware/000077500000000000000000000000001463735525100156455ustar00rootroot00000000000000smithy-go-1.20.3/middleware/doc.go000066400000000000000000000064231463735525100167460ustar00rootroot00000000000000// Package middleware provides transport agnostic middleware for decorating SDK // handlers. // // The Smithy middleware stack provides ordered behavior to be invoked on an // underlying handler. The stack is separated into steps that are invoked in a // static order. A step is a collection of middleware that are injected into a // ordered list defined by the user. The user may add, insert, swap, and remove a // step's middleware. When the stack is invoked the step middleware become static, // and their order cannot be modified. // // A stack and its step middleware are **not** safe to modify concurrently. // // A stack will use the ordered list of middleware to decorate a underlying // handler. A handler could be something like an HTTP Client that round trips an // API operation over HTTP. // // Smithy Middleware Stack // // A Stack is a collection of middleware that wrap a handler. The stack can be // broken down into discreet steps. Each step may contain zero or more middleware // specific to that stack's step. // // A Stack Step is a predefined set of middleware that are invoked in a static // order by the Stack. These steps represent fixed points in the middleware stack // for organizing specific behavior, such as serialize and build. A Stack Step is // composed of zero or more middleware that are specific to that step. A step may // define its own set of input/output parameters the generic input/output // parameters are cast from. A step calls its middleware recursively, before // calling the next step in the stack returning the result or error of the step // middleware decorating the underlying handler. // // * Initialize: Prepares the input, and sets any default parameters as needed, // (e.g. idempotency token, and presigned URLs). // // * Serialize: Serializes the prepared input into a data structure that can be // consumed by the target transport's message, (e.g. REST-JSON serialization). // // * Build: Adds additional metadata to the serialized transport message, (e.g. // HTTP's Content-Length header, or body checksum). Decorations and // modifications to the message should be copied to all message attempts. // // * Finalize: Performs final preparations needed before sending the message. The // message should already be complete by this stage, and is only alternated to // meet the expectations of the recipient, (e.g. Retry and AWS SigV4 request // signing). // // * Deserialize: Reacts to the handler's response returned by the recipient of // the request message. Deserializes the response into a structured type or // error above stacks can react to. // // Adding Middleware to a Stack Step // // Middleware can be added to a step front or back, or relative, by name, to an // existing middleware in that stack. If a middleware does not have a name, a // unique name will be generated at the middleware and be added to the step. // // // Create middleware stack // stack := middleware.NewStack() // // // Add middleware to stack steps // stack.Initialize.Add(paramValidationMiddleware, middleware.After) // stack.Serialize.Add(marshalOperationFoo, middleware.After) // stack.Deserialize.Add(unmarshalOperationFoo, middleware.After) // // // Invoke middleware on handler. // resp, err := stack.HandleMiddleware(ctx, req.Input, clientHandler) package middleware smithy-go-1.20.3/middleware/logging.go000066400000000000000000000027531463735525100176310ustar00rootroot00000000000000package middleware import ( "context" "github.com/aws/smithy-go/logging" ) // loggerKey is the context value key for which the logger is associated with. type loggerKey struct{} // GetLogger takes a context to retrieve a Logger from. If no logger is present on the context a logging.Nop logger // is returned. If the logger retrieved from context supports the ContextLogger interface, the context will be passed // to the WithContext method and the resulting logger will be returned. Otherwise the stored logger is returned as is. func GetLogger(ctx context.Context) logging.Logger { logger, ok := ctx.Value(loggerKey{}).(logging.Logger) if !ok || logger == nil { return logging.Nop{} } return logging.WithContext(ctx, logger) } // SetLogger sets the provided logger value on the provided ctx. func SetLogger(ctx context.Context, logger logging.Logger) context.Context { return context.WithValue(ctx, loggerKey{}, logger) } type setLogger struct { Logger logging.Logger } // AddSetLoggerMiddleware adds a middleware that will add the provided logger to the middleware context. func AddSetLoggerMiddleware(stack *Stack, logger logging.Logger) error { return stack.Initialize.Add(&setLogger{Logger: logger}, After) } func (a *setLogger) ID() string { return "SetLogger" } func (a *setLogger) HandleInitialize(ctx context.Context, in InitializeInput, next InitializeHandler) ( out InitializeOutput, metadata Metadata, err error, ) { return next.HandleInitialize(SetLogger(ctx, a.Logger), in) } smithy-go-1.20.3/middleware/logging_test.go000066400000000000000000000024401463735525100206610ustar00rootroot00000000000000package middleware_test import ( "context" "io/ioutil" "testing" "github.com/aws/smithy-go/logging" "github.com/aws/smithy-go/middleware" ) type mockWithContextLogger struct { logging.Logger Context context.Context } func (m mockWithContextLogger) WithContext(ctx context.Context) logging.Logger { m.Context = ctx return m } func TestGetLogger(t *testing.T) { if logger := middleware.GetLogger(context.Background()); logger == nil { t.Fatal("expect logger to not be nil") } else if _, ok := logger.(logging.Nop); !ok { t.Fatal("expect GetLogger to fallback to Nop") } standardLogger := logging.NewStandardLogger(ioutil.Discard) ctx := middleware.SetLogger(context.Background(), standardLogger) if logger := middleware.GetLogger(ctx); logger == nil { t.Fatal("expect logger to not be nil") } else if logger != standardLogger { t.Error("expect logger to be standard logger") } withContextLogger := mockWithContextLogger{} ctx = middleware.SetLogger(context.Background(), withContextLogger) if logger := middleware.GetLogger(ctx); logger == nil { t.Fatal("expect logger to not be nil") } else if mock, ok := logger.(mockWithContextLogger); !ok { t.Error("expect logger to be context logger") } else if mock.Context != ctx { t.Error("expect logger context to match") } } smithy-go-1.20.3/middleware/metadata.go000066400000000000000000000034501463735525100177560ustar00rootroot00000000000000package middleware // MetadataReader provides an interface for reading metadata from the // underlying metadata container. type MetadataReader interface { Get(key interface{}) interface{} } // Metadata provides storing and reading metadata values. Keys may be any // comparable value type. Get and set will panic if key is not a comparable // value type. // // Metadata uses lazy initialization, and Set method must be called as an // addressable value, or pointer. Not doing so may cause key/value pair to not // be set. type Metadata struct { values map[interface{}]interface{} } // Get attempts to retrieve the value the key points to. Returns nil if the // key was not found. // // Panics if key type is not comparable. func (m Metadata) Get(key interface{}) interface{} { return m.values[key] } // Clone creates a shallow copy of Metadata entries, returning a new Metadata // value with the original entries copied into it. func (m Metadata) Clone() Metadata { vs := make(map[interface{}]interface{}, len(m.values)) for k, v := range m.values { vs[k] = v } return Metadata{ values: vs, } } // Set stores the value pointed to by the key. If a value already exists at // that key it will be replaced with the new value. // // Set method must be called as an addressable value, or pointer. If Set is not // called as an addressable value or pointer, the key value pair being set may // be lost. // // Panics if the key type is not comparable. func (m *Metadata) Set(key, value interface{}) { if m.values == nil { m.values = map[interface{}]interface{}{} } m.values[key] = value } // Has returns whether the key exists in the metadata. // // Panics if the key type is not comparable. func (m Metadata) Has(key interface{}) bool { if m.values == nil { return false } _, ok := m.values[key] return ok } smithy-go-1.20.3/middleware/metadata_test.go000066400000000000000000000010301463735525100210050ustar00rootroot00000000000000package middleware import "testing" func TestMetadataClone(t *testing.T) { original := map[interface{}]interface{}{ "abc": 123, "efg": "hij", } var m Metadata for k, v := range original { m.Set(k, v) } o := m.Clone() o.Set("unique", "value") for k := range original { if !o.Has(k) { t.Errorf("expect %v to be in cloned metadata", k) } } if !o.Has("unique") { t.Errorf("expect cloned metadata to have new entry") } if m.Has("unique") { t.Errorf("expect cloned metadata to not leak in to original") } } smithy-go-1.20.3/middleware/middleware.go000066400000000000000000000041101463735525100203050ustar00rootroot00000000000000package middleware import ( "context" ) // Handler provides the interface for performing the logic to obtain an output, // or error for the given input. type Handler interface { // Handle performs logic to obtain an output for the given input. Handler // should be decorated with middleware to perform input specific behavior. Handle(ctx context.Context, input interface{}) ( output interface{}, metadata Metadata, err error, ) } // HandlerFunc provides a wrapper around a function pointer to be used as a // middleware handler. type HandlerFunc func(ctx context.Context, input interface{}) ( output interface{}, metadata Metadata, err error, ) // Handle invokes the underlying function, returning the result. func (fn HandlerFunc) Handle(ctx context.Context, input interface{}) ( output interface{}, metadata Metadata, err error, ) { return fn(ctx, input) } // Middleware provides the interface to call handlers in a chain. type Middleware interface { // ID provides a unique identifier for the middleware. ID() string // Performs the middleware's handling of the input, returning the output, // or error. The middleware can invoke the next Handler if handling should // continue. HandleMiddleware(ctx context.Context, input interface{}, next Handler) ( output interface{}, metadata Metadata, err error, ) } // decoratedHandler wraps a middleware in order to to call the next handler in // the chain. type decoratedHandler struct { // The next handler to be called. Next Handler // The current middleware decorating the handler. With Middleware } // Handle implements the Handler interface to handle a operation invocation. func (m decoratedHandler) Handle(ctx context.Context, input interface{}) ( output interface{}, metadata Metadata, err error, ) { return m.With.HandleMiddleware(ctx, input, m.Next) } // DecorateHandler decorates a handler with a middleware. Wrapping the handler // with the middleware. func DecorateHandler(h Handler, with ...Middleware) Handler { for i := len(with) - 1; i >= 0; i-- { h = decoratedHandler{ Next: h, With: with[i], } } return h } smithy-go-1.20.3/middleware/middleware_test.go000066400000000000000000000030451463735525100213520ustar00rootroot00000000000000package middleware import ( "context" "fmt" "testing" ) var _ Handler = (HandlerFunc)(nil) var _ Handler = (decoratedHandler{}) type mockMiddleware struct { id int } func (m mockMiddleware) ID() string { return fmt.Sprintf("mock middleware %d", m.id) } func (m mockMiddleware) HandleMiddleware(ctx context.Context, input interface{}, next Handler) ( output interface{}, metadata Metadata, err error, ) { output, metadata, err = next.Handle(ctx, input) mockKeySet(&metadata, m.id, fmt.Sprintf("mock-%d", m.id)) return output, metadata, err } type mockKey struct{ Key int } func mockKeySet(md *Metadata, key int, val string) { md.Set(mockKey{Key: key}, val) } func mockKeyGet(md MetadataReader, key int) string { v := md.Get(mockKey{Key: key}) if v == nil { return "" } return v.(string) } type mockHandler struct { } func (m *mockHandler) Handle(ctx context.Context, input interface{}) ( output interface{}, metadata Metadata, err error, ) { return nil, metadata, nil } func TestDecorateHandler(t *testing.T) { mockHandler := &mockHandler{} h := DecorateHandler( mockHandler, mockMiddleware{id: 0}, mockMiddleware{id: 1}, mockMiddleware{id: 2}, ) _, metadata, err := h.Handle(context.Background(), struct{}{}) if err != nil { t.Fatalf("expect no error, got %v", err) } expectMeta := map[int]interface{}{ 0: "mock-0", 1: "mock-1", 2: "mock-2", } for key, expect := range expectMeta { v := mockKeyGet(metadata, key) if e, a := expect, v; e != a { t.Errorf("expect %v: %v metadata got %v", key, e, a) } } } smithy-go-1.20.3/middleware/ordered_group.go000066400000000000000000000134501463735525100210370ustar00rootroot00000000000000package middleware import "fmt" // RelativePosition provides specifying the relative position of a middleware // in an ordered group. type RelativePosition int // Relative position for middleware in steps. const ( After RelativePosition = iota Before ) type ider interface { ID() string } // orderedIDs provides an ordered collection of items with relative ordering // by name. type orderedIDs struct { order *relativeOrder items map[string]ider } const baseOrderedItems = 5 func newOrderedIDs() *orderedIDs { return &orderedIDs{ order: newRelativeOrder(), items: make(map[string]ider, baseOrderedItems), } } // Add injects the item to the relative position of the item group. Returns an // error if the item already exists. func (g *orderedIDs) Add(m ider, pos RelativePosition) error { id := m.ID() if len(id) == 0 { return fmt.Errorf("empty ID, ID must not be empty") } if err := g.order.Add(pos, id); err != nil { return err } g.items[id] = m return nil } // Insert injects the item relative to an existing item id. Returns an error if // the original item does not exist, or the item being added already exists. func (g *orderedIDs) Insert(m ider, relativeTo string, pos RelativePosition) error { if len(m.ID()) == 0 { return fmt.Errorf("insert ID must not be empty") } if len(relativeTo) == 0 { return fmt.Errorf("relative to ID must not be empty") } if err := g.order.Insert(relativeTo, pos, m.ID()); err != nil { return err } g.items[m.ID()] = m return nil } // Get returns the ider identified by id. If ider is not present, returns false. func (g *orderedIDs) Get(id string) (ider, bool) { v, ok := g.items[id] return v, ok } // Swap removes the item by id, replacing it with the new item. Returns an error // if the original item doesn't exist. func (g *orderedIDs) Swap(id string, m ider) (ider, error) { if len(id) == 0 { return nil, fmt.Errorf("swap from ID must not be empty") } iderID := m.ID() if len(iderID) == 0 { return nil, fmt.Errorf("swap to ID must not be empty") } if err := g.order.Swap(id, iderID); err != nil { return nil, err } removed := g.items[id] delete(g.items, id) g.items[iderID] = m return removed, nil } // Remove removes the item by id. Returns an error if the item // doesn't exist. func (g *orderedIDs) Remove(id string) (ider, error) { if len(id) == 0 { return nil, fmt.Errorf("remove ID must not be empty") } if err := g.order.Remove(id); err != nil { return nil, err } removed := g.items[id] delete(g.items, id) return removed, nil } func (g *orderedIDs) List() []string { items := g.order.List() order := make([]string, len(items)) copy(order, items) return order } // Clear removes all entries and slots. func (g *orderedIDs) Clear() { g.order.Clear() g.items = map[string]ider{} } // GetOrder returns the item in the order it should be invoked in. func (g *orderedIDs) GetOrder() []interface{} { order := g.order.List() ordered := make([]interface{}, len(order)) for i := 0; i < len(order); i++ { ordered[i] = g.items[order[i]] } return ordered } // relativeOrder provides ordering of item type relativeOrder struct { order []string } func newRelativeOrder() *relativeOrder { return &relativeOrder{ order: make([]string, 0, baseOrderedItems), } } // Add inserts an item into the order relative to the position provided. func (s *relativeOrder) Add(pos RelativePosition, ids ...string) error { if len(ids) == 0 { return nil } for _, id := range ids { if _, ok := s.has(id); ok { return fmt.Errorf("already exists, %v", id) } } switch pos { case Before: return s.insert(0, Before, ids...) case After: s.order = append(s.order, ids...) default: return fmt.Errorf("invalid position, %v", int(pos)) } return nil } // Insert injects an item before or after the relative item. Returns // an error if the relative item does not exist. func (s *relativeOrder) Insert(relativeTo string, pos RelativePosition, ids ...string) error { if len(ids) == 0 { return nil } for _, id := range ids { if _, ok := s.has(id); ok { return fmt.Errorf("already exists, %v", id) } } i, ok := s.has(relativeTo) if !ok { return fmt.Errorf("not found, %v", relativeTo) } return s.insert(i, pos, ids...) } // Swap will replace the item id with the to item. Returns an // error if the original item id does not exist. Allows swapping out an // item for another item with the same id. func (s *relativeOrder) Swap(id, to string) error { i, ok := s.has(id) if !ok { return fmt.Errorf("not found, %v", id) } if _, ok = s.has(to); ok && id != to { return fmt.Errorf("already exists, %v", to) } s.order[i] = to return nil } func (s *relativeOrder) Remove(id string) error { i, ok := s.has(id) if !ok { return fmt.Errorf("not found, %v", id) } s.order = append(s.order[:i], s.order[i+1:]...) return nil } func (s *relativeOrder) List() []string { return s.order } func (s *relativeOrder) Clear() { s.order = s.order[0:0] } func (s *relativeOrder) insert(i int, pos RelativePosition, ids ...string) error { switch pos { case Before: n := len(ids) var src []string if n <= cap(s.order)-len(s.order) { s.order = s.order[:len(s.order)+n] src = s.order } else { src = s.order s.order = make([]string, len(s.order)+n) copy(s.order[:i], src[:i]) // only when allocating a new slice do we need to copy the front half } copy(s.order[i+n:], src[i:]) copy(s.order[i:], ids) case After: if i == len(s.order)-1 || len(s.order) == 0 { s.order = append(s.order, ids...) } else { s.order = append(s.order[:i+1], append(ids, s.order[i+1:]...)...) } default: return fmt.Errorf("invalid position, %v", int(pos)) } return nil } func (s *relativeOrder) has(id string) (i int, found bool) { for i := 0; i < len(s.order); i++ { if s.order[i] == id { return i, true } } return 0, false } smithy-go-1.20.3/middleware/ordered_group_test.go000066400000000000000000000155311463735525100221000ustar00rootroot00000000000000package middleware import ( "reflect" "testing" ) func TestOrderedIDsAdd(t *testing.T) { o := newOrderedIDs() noError(t, o.Add(&mockIder{"first"}, After)) noError(t, o.Add(&mockIder{"second"}, After)) noError(t, o.Add(&mockIder{"third"}, After)) noError(t, o.Add(&mockIder{"real-first"}, Before)) if err := o.Add(&mockIder{""}, After); err == nil { t.Errorf("expect error adding empty ID, got none") } if err := o.Add(&mockIder{"second"}, After); err == nil { t.Errorf("expect error adding duplicate, got none") } if err := o.Add(&mockIder{"unique"}, 123); err == nil { t.Errorf("expect error add unknown relative position, got none") } expectIDs := []string{"real-first", "first", "second", "third"} if e, a := expectIDs, o.List(); !reflect.DeepEqual(e, a) { t.Errorf("expect %v order, got %v", e, a) } } func TestOrderedIDsInsert(t *testing.T) { o := newOrderedIDs() noError(t, o.Add(&mockIder{"first"}, After)) noError(t, o.Insert(&mockIder{"third"}, "first", After)) noError(t, o.Insert(&mockIder{"second"}, "third", Before)) noError(t, o.Insert(&mockIder{"real-first"}, "first", Before)) noError(t, o.Insert(&mockIder{"not-yet-last"}, "second", After)) noError(t, o.Insert(&mockIder{"last"}, "third", After)) if err := o.Insert(&mockIder{""}, "first", After); err == nil { t.Errorf("expect error insert empty ID, got none") } if err := o.Insert(&mockIder{"second"}, "", After); err == nil { t.Errorf("expect error insert with empty relative ID, got none") } if err := o.Insert(&mockIder{"second"}, "third", After); err == nil { t.Errorf("expect error insert duplicate, got none") } if err := o.Insert(&mockIder{"unique"}, "not-found", After); err == nil { t.Errorf("expect error insert not found relative ID, got none") } if err := o.Insert(&mockIder{"unique"}, "first", 123); err == nil { t.Errorf("expect error insert unknown relative position, got none") } expectIDs := []string{"real-first", "first", "second", "not-yet-last", "third", "last"} if e, a := expectIDs, o.List(); !reflect.DeepEqual(e, a) { t.Errorf("expect %v order, got %v", e, a) } } func TestOrderedIDsGet(t *testing.T) { o := newOrderedIDs() noError(t, o.Add(&mockIder{"first"}, After)) noError(t, o.Add(&mockIder{"second"}, After)) f, ok := o.Get("not-found") if ok || f != nil { t.Fatalf("expect id not to be found, but was") } f, ok = o.Get("first") if !ok { t.Fatalf("expect id to be found, was not") } if e, a := "first", f.ID(); e != a { t.Errorf("expect %v id, got %v", e, a) } } func TestOrderedIDsSwap(t *testing.T) { o := newOrderedIDs() noError(t, o.Add(&mockIder{"first"}, After)) noError(t, o.Add(&mockIder{"second"}, After)) noError(t, o.Add(&mockIder{"third"}, After)) if _, err := o.Swap("first", &mockIder{""}); err == nil { t.Errorf("expect error swap empty ID, got none") } if _, err := o.Swap("", &mockIder{"second"}); err == nil { t.Errorf("expect error swap with empty relative ID, got none") } if _, err := o.Swap("not-exists", &mockIder{"last"}); err == nil { t.Errorf("expect error swap not-exists ID, got none") } if _, err := o.Swap("second", &mockIder{"first"}); err == nil { t.Errorf("expect error swap to existing ID, got none") } r, err := o.Swap("second", &mockIder{"otherSecond"}) noError(t, err) if r == nil { t.Fatalf("expect removed item to be returned") } if e, a := "second", r.ID(); e != a { t.Errorf("expect %v removed ider, got %v", e, a) } expectIDs := []string{"first", "otherSecond", "third"} if e, a := expectIDs, o.List(); !reflect.DeepEqual(e, a) { t.Errorf("expect %v order, got %v", e, a) } } func TestOrderedIDsRemove(t *testing.T) { o := newOrderedIDs() firstIder := &mockIder{"first"} noError(t, o.Add(firstIder, After)) noError(t, o.Insert(&mockIder{"third"}, "first", After)) if removed, err := o.Remove("first"); err != nil { t.Errorf("expect no error, got %v", err) } else if removed != firstIder { t.Error("removed ider did not match expected") } noError(t, o.Insert(&mockIder{"last"}, "third", After)) if _, err := o.Remove(""); err == nil { t.Errorf("expect error remove empty ID, got none") } if _, err := o.Remove("not-exists"); err == nil { t.Errorf("expect error remove not exists ID, got none") } expectIDs := []string{"third", "last"} if e, a := expectIDs, o.List(); !reflect.DeepEqual(e, a) { t.Errorf("expect %v order, got %v", e, a) } } func TestOrderedIDsClear(t *testing.T) { o := newOrderedIDs() noError(t, o.Add(&mockIder{"first"}, After)) noError(t, o.Add(&mockIder{"second"}, After)) o.Clear() noError(t, o.Add(&mockIder{"third"}, After)) noError(t, o.Add(&mockIder{"fourth"}, After)) expectIDs := []string{"third", "fourth"} if e, a := expectIDs, o.List(); !reflect.DeepEqual(e, a) { t.Errorf("expect %v order, got %v", e, a) } } func TestOrderedIDsGetOrder(t *testing.T) { o := newOrderedIDs() noError(t, o.Add(&mockIder{"first"}, After)) noError(t, o.Add(&mockIder{"second"}, After)) noError(t, o.Add(&mockIder{"third"}, After)) noError(t, o.Add(&mockIder{"real-first"}, Before)) expectIDs := []string{"real-first", "first", "second", "third"} if e, a := expectIDs, o.List(); !reflect.DeepEqual(e, a) { t.Errorf("expect %v order, got %v", e, a) } actualOrder := o.GetOrder() if e, a := len(expectIDs), len(actualOrder); e != a { t.Errorf("expect %v IDs, got %v", e, a) } compareGetOrder(t, expectIDs, actualOrder) } func TestRelativeOrder_insert(t *testing.T) { var ro relativeOrder if err := ro.insert(0, After); err != nil { t.Errorf("expect no error, got %v", err) } if err := ro.insert(0, Before); err != nil { t.Errorf("expect no error, got %v", err) } if len(ro.order) > 0 { t.Errorf("expect slice to be empty") } if err := ro.insert(0, After, "foo"); err != nil { t.Errorf("expect no error, got %v", err) } if err := ro.insert(0, After, "bar"); err != nil { t.Errorf("expect no error, got %v", err) } if err := ro.insert(1, After, "baz"); err != nil { t.Errorf("expect no error, got %v", err) } if err := ro.insert(1, Before, "fob"); err != nil { t.Errorf("expect no error, got %v", err) } if err := ro.insert(3, Before, "bas"); err != nil { t.Errorf("expect no error, got %v", err) } if err := ro.insert(3, After, "bat"); err != nil { t.Errorf("expect no error, got %v", err) } if err := ro.insert(0, 1024, "zzz"); err == nil { t.Error("expect error, got nil") } expect := []string{"foo", "fob", "bar", "bas", "bat", "baz"} if !reflect.DeepEqual(expect, ro.order) { t.Errorf("%v != %v", expect, ro.order) } } func compareGetOrder(t *testing.T, expected []string, actual []interface{}) { t.Helper() for _, eID := range expected { var found bool for _, aIder := range actual { if e, a := eID, aIder.(ider).ID(); e == a { if found { t.Errorf("expect only one %v, got more", e) } found = true } } if !found { t.Errorf("expect to find %v, did not", eID) } } } smithy-go-1.20.3/middleware/shared_test.go000066400000000000000000000032301463735525100204770ustar00rootroot00000000000000package middleware import ( "context" "testing" ) type mockIder struct { Identifier string } func (m *mockIder) ID() string { return m.Identifier } func noError(t *testing.T, err error) { t.Helper() if err != nil { t.Errorf("expect no error, got %v", err) } } func mockInitializeMiddleware(id string) InitializeMiddleware { return InitializeMiddlewareFunc(id, func( ctx context.Context, in InitializeInput, next InitializeHandler, ) ( out InitializeOutput, metadata Metadata, err error, ) { return next.HandleInitialize(ctx, in) }) } func mockSerializeMiddleware(id string) SerializeMiddleware { return SerializeMiddlewareFunc(id, func( ctx context.Context, in SerializeInput, next SerializeHandler, ) ( out SerializeOutput, metadata Metadata, err error, ) { return next.HandleSerialize(ctx, in) }) } func mockBuildMiddleware(id string) BuildMiddleware { return BuildMiddlewareFunc(id, func( ctx context.Context, in BuildInput, next BuildHandler, ) ( out BuildOutput, metadata Metadata, err error, ) { return next.HandleBuild(ctx, in) }) } func mockFinalizeMiddleware(id string) FinalizeMiddleware { return FinalizeMiddlewareFunc(id, func( ctx context.Context, in FinalizeInput, next FinalizeHandler, ) ( out FinalizeOutput, metadata Metadata, err error, ) { return next.HandleFinalize(ctx, in) }) } func mockDeserializeMiddleware(id string) DeserializeMiddleware { return DeserializeMiddlewareFunc(id, func( ctx context.Context, in DeserializeInput, next DeserializeHandler, ) ( out DeserializeOutput, metadata Metadata, err error, ) { return next.HandleDeserialize(ctx, in) }) } smithy-go-1.20.3/middleware/stack.go000066400000000000000000000124411463735525100173030ustar00rootroot00000000000000package middleware import ( "context" "io" "strings" ) // Stack provides protocol and transport agnostic set of middleware split into // distinct steps. Steps have specific transitions between them, that are // managed by the individual step. // // Steps are composed as middleware around the underlying handler in the // following order: // // Initialize -> Serialize -> Build -> Finalize -> Deserialize -> Handler // // Any middleware within the chain may choose to stop and return an error or // response. Since the middleware decorate the handler like a call stack, each // middleware will receive the result of the next middleware in the chain. // Middleware that does not need to react to an input, or result must forward // along the input down the chain, or return the result back up the chain. // // Initialize <- Serialize -> Build -> Finalize <- Deserialize <- Handler type Stack struct { // Initialize prepares the input, and sets any default parameters as // needed, (e.g. idempotency token, and presigned URLs). // // Takes Input Parameters, and returns result or error. // // Receives result or error from Serialize step. Initialize *InitializeStep // Serialize serializes the prepared input into a data structure that can be consumed // by the target transport's message, (e.g. REST-JSON serialization) // // Converts Input Parameters into a Request, and returns the result or error. // // Receives result or error from Build step. Serialize *SerializeStep // Build adds additional metadata to the serialized transport message // (e.g. HTTP's Content-Length header, or body checksum). Decorations and // modifications to the message should be copied to all message attempts. // // Takes Request, and returns result or error. // // Receives result or error from Finalize step. Build *BuildStep // Finalize performs final preparations needed before sending the message. The // message should already be complete by this stage, and is only alternated // to meet the expectations of the recipient (e.g. Retry and AWS SigV4 // request signing) // // Takes Request, and returns result or error. // // Receives result or error from Deserialize step. Finalize *FinalizeStep // Deserialize reacts to the handler's response returned by the recipient of the request // message. Deserializes the response into a structured type or error above // stacks can react to. // // Should only forward Request to underlying handler. // // Takes Request, and returns result or error. // // Receives raw response, or error from underlying handler. Deserialize *DeserializeStep id string } // NewStack returns an initialize empty stack. func NewStack(id string, newRequestFn func() interface{}) *Stack { return &Stack{ id: id, Initialize: NewInitializeStep(), Serialize: NewSerializeStep(newRequestFn), Build: NewBuildStep(), Finalize: NewFinalizeStep(), Deserialize: NewDeserializeStep(), } } // ID returns the unique ID for the stack as a middleware. func (s *Stack) ID() string { return s.id } // HandleMiddleware invokes the middleware stack decorating the next handler. // Each step of stack will be invoked in order before calling the next step. // With the next handler call last. // // The input value must be the input parameters of the operation being // performed. // // Will return the result of the operation, or error. func (s *Stack) HandleMiddleware(ctx context.Context, input interface{}, next Handler) ( output interface{}, metadata Metadata, err error, ) { h := DecorateHandler(next, s.Initialize, s.Serialize, s.Build, s.Finalize, s.Deserialize, ) return h.Handle(ctx, input) } // List returns a list of all middleware in the stack by step. func (s *Stack) List() []string { var l []string l = append(l, s.id) l = append(l, s.Initialize.ID()) l = append(l, s.Initialize.List()...) l = append(l, s.Serialize.ID()) l = append(l, s.Serialize.List()...) l = append(l, s.Build.ID()) l = append(l, s.Build.List()...) l = append(l, s.Finalize.ID()) l = append(l, s.Finalize.List()...) l = append(l, s.Deserialize.ID()) l = append(l, s.Deserialize.List()...) return l } func (s *Stack) String() string { var b strings.Builder w := &indentWriter{w: &b} w.WriteLine(s.id) w.Push() writeStepItems(w, s.Initialize) writeStepItems(w, s.Serialize) writeStepItems(w, s.Build) writeStepItems(w, s.Finalize) writeStepItems(w, s.Deserialize) return b.String() } type stackStepper interface { ID() string List() []string } func writeStepItems(w *indentWriter, s stackStepper) { type lister interface { List() []string } w.WriteLine(s.ID()) w.Push() defer w.Pop() // ignore stack to prevent circular iterations if _, ok := s.(*Stack); ok { return } for _, id := range s.List() { w.WriteLine(id) } } type stringWriter interface { io.Writer WriteString(string) (int, error) WriteRune(rune) (int, error) } type indentWriter struct { w stringWriter depth int } const indentDepth = "\t\t\t\t\t\t\t\t\t\t" func (w *indentWriter) Push() { w.depth++ } func (w *indentWriter) Pop() { w.depth-- if w.depth < 0 { w.depth = 0 } } func (w *indentWriter) WriteLine(v string) { w.w.WriteString(indentDepth[:w.depth]) v = strings.ReplaceAll(v, "\n", "\\n") v = strings.ReplaceAll(v, "\r", "\\r") w.w.WriteString(v) w.w.WriteRune('\n') } smithy-go-1.20.3/middleware/stack_test.go000066400000000000000000000033121463735525100203370ustar00rootroot00000000000000package middleware import ( "reflect" "strings" "testing" ) func TestStackList(t *testing.T) { s := NewStack("fooStack", func() interface{} { return struct{}{} }) s.Initialize.Add(mockInitializeMiddleware("first"), After) s.Serialize.Add(mockSerializeMiddleware("second"), After) s.Build.Add(mockBuildMiddleware("third"), After) s.Finalize.Add(mockFinalizeMiddleware("fourth"), After) s.Deserialize.Add(mockDeserializeMiddleware("fifth"), After) actual := s.List() expect := []string{ "fooStack", (*InitializeStep)(nil).ID(), "first", (*SerializeStep)(nil).ID(), "second", (*BuildStep)(nil).ID(), "third", (*FinalizeStep)(nil).ID(), "fourth", (*DeserializeStep)(nil).ID(), "fifth", } if !reflect.DeepEqual(expect, actual) { t.Errorf("expect and actual stack list differ: %v != %v", expect, actual) } } func TestStackString(t *testing.T) { s := NewStack("fooStack", func() interface{} { return struct{}{} }) s.Initialize.Add(mockInitializeMiddleware("first"), After) s.Serialize.Add(mockSerializeMiddleware("second"), After) s.Build.Add(mockBuildMiddleware("third"), After) s.Finalize.Add(mockFinalizeMiddleware("fourth"), After) s.Deserialize.Add(mockDeserializeMiddleware("fifth"), After) actual := s.String() expect := strings.Join([]string{ "fooStack", "\t" + (*InitializeStep)(nil).ID(), "\t\t" + "first", "\t" + (*SerializeStep)(nil).ID(), "\t\t" + "second", "\t" + (*BuildStep)(nil).ID(), "\t\t" + "third", "\t" + (*FinalizeStep)(nil).ID(), "\t\t" + "fourth", "\t" + (*DeserializeStep)(nil).ID(), "\t\t" + "fifth", "", }, "\n") if !reflect.DeepEqual(expect, actual) { t.Errorf("expect and actual stack list differ: %v != %v", expect, actual) } } smithy-go-1.20.3/middleware/stack_values.go000066400000000000000000000041721463735525100206640ustar00rootroot00000000000000package middleware import ( "context" "reflect" "strings" ) // WithStackValue adds a key value pair to the context that is intended to be // scoped to a stack. Use ClearStackValues to get a new context with all stack // values cleared. func WithStackValue(ctx context.Context, key, value interface{}) context.Context { md, _ := ctx.Value(stackValuesKey{}).(*stackValues) md = withStackValue(md, key, value) return context.WithValue(ctx, stackValuesKey{}, md) } // ClearStackValues returns a context without any stack values. func ClearStackValues(ctx context.Context) context.Context { return context.WithValue(ctx, stackValuesKey{}, nil) } // GetStackValues returns the value pointed to by the key within the stack // values, if it is present. func GetStackValue(ctx context.Context, key interface{}) interface{} { md, _ := ctx.Value(stackValuesKey{}).(*stackValues) if md == nil { return nil } return md.Value(key) } type stackValuesKey struct{} type stackValues struct { key interface{} value interface{} parent *stackValues } func withStackValue(parent *stackValues, key, value interface{}) *stackValues { if key == nil { panic("nil key") } if !reflect.TypeOf(key).Comparable() { panic("key is not comparable") } return &stackValues{key: key, value: value, parent: parent} } func (m *stackValues) Value(key interface{}) interface{} { if key == m.key { return m.value } if m.parent == nil { return nil } return m.parent.Value(key) } func (c *stackValues) String() string { var str strings.Builder cc := c for cc == nil { str.WriteString("(" + reflect.TypeOf(c.key).String() + ": " + stringify(cc.value) + ")") if cc.parent != nil { str.WriteString(" -> ") } cc = cc.parent } str.WriteRune('}') return str.String() } type stringer interface { String() string } // stringify tries a bit to stringify v, without using fmt, since we don't // want context depending on the unicode tables. This is only used by // *valueCtx.String(). func stringify(v interface{}) string { switch s := v.(type) { case stringer: return s.String() case string: return s } return "" } smithy-go-1.20.3/middleware/stack_values_test.go000066400000000000000000000024251463735525100217220ustar00rootroot00000000000000package middleware import ( "context" "testing" ) func TestStackValues(t *testing.T) { ctx := context.Background() // Ensure empty stack values don't return something if v := GetStackValue(ctx, "some key"); v != nil { t.Fatalf("expect not-existing key to be nil, got %T, %v", v, v) } // Add a stack values, ensure not polluting previous context. ctx2 := WithStackValue(ctx, "some key", "foo") ctx2 = WithStackValue(ctx2, "some other key", "bar") if v := GetStackValue(ctx, "some key"); v != nil { t.Fatalf("expect not-existing key to be nil, got %T, %v", v, v) } if v, ok := GetStackValue(ctx2, "some key").(string); !ok || v != "foo" { t.Fatalf("expect key to be present") } if v, ok := GetStackValue(ctx2, "some other key").(string); !ok || v != "bar" { t.Fatalf("expect key to be present") } // Clear the stack values ensure new context doesn't have any stack values. ctx3 := ClearStackValues(ctx2) if v, ok := GetStackValue(ctx2, "some key").(string); !ok || v != "foo" { t.Fatalf("expect key to be present") } if v := GetStackValue(ctx3, "some key"); v != nil { t.Fatalf("expect not-existing key to be nil, got %T, %v", v, v) } if v := GetStackValue(ctx3, "some other key"); v != nil { t.Fatalf("expect not-existing key to be nil, got %T, %v", v, v) } } smithy-go-1.20.3/middleware/step_build.go000066400000000000000000000137331463735525100203350ustar00rootroot00000000000000package middleware import ( "context" ) // BuildInput provides the input parameters for the BuildMiddleware to consume. // BuildMiddleware may modify the Request value before forwarding the input // along to the next BuildHandler. type BuildInput struct { Request interface{} } // BuildOutput provides the result returned by the next BuildHandler. type BuildOutput struct { Result interface{} } // BuildHandler provides the interface for the next handler the // BuildMiddleware will call in the middleware chain. type BuildHandler interface { HandleBuild(ctx context.Context, in BuildInput) ( out BuildOutput, metadata Metadata, err error, ) } // BuildMiddleware provides the interface for middleware specific to the // serialize step. Delegates to the next BuildHandler for further // processing. type BuildMiddleware interface { // Unique ID for the middleware in theBuildStep. The step does not allow // duplicate IDs. ID() string // Invokes the middleware behavior which must delegate to the next handler // for the middleware chain to continue. The method must return a result or // error to its caller. HandleBuild(ctx context.Context, in BuildInput, next BuildHandler) ( out BuildOutput, metadata Metadata, err error, ) } // BuildMiddlewareFunc returns a BuildMiddleware with the unique ID provided, // and the func to be invoked. func BuildMiddlewareFunc(id string, fn func(context.Context, BuildInput, BuildHandler) (BuildOutput, Metadata, error)) BuildMiddleware { return buildMiddlewareFunc{ id: id, fn: fn, } } type buildMiddlewareFunc struct { // Unique ID for the middleware. id string // Middleware function to be called. fn func(context.Context, BuildInput, BuildHandler) (BuildOutput, Metadata, error) } // ID returns the unique ID for the middleware. func (s buildMiddlewareFunc) ID() string { return s.id } // HandleBuild invokes the middleware Fn. func (s buildMiddlewareFunc) HandleBuild(ctx context.Context, in BuildInput, next BuildHandler) ( out BuildOutput, metadata Metadata, err error, ) { return s.fn(ctx, in, next) } var _ BuildMiddleware = (buildMiddlewareFunc{}) // BuildStep provides the ordered grouping of BuildMiddleware to be invoked on // a handler. type BuildStep struct { ids *orderedIDs } // NewBuildStep returns a BuildStep ready to have middleware for // initialization added to it. func NewBuildStep() *BuildStep { return &BuildStep{ ids: newOrderedIDs(), } } var _ Middleware = (*BuildStep)(nil) // ID returns the unique name of the step as a middleware. func (s *BuildStep) ID() string { return "Build stack step" } // HandleMiddleware invokes the middleware by decorating the next handler // provided. Returns the result of the middleware and handler being invoked. // // Implements Middleware interface. func (s *BuildStep) HandleMiddleware(ctx context.Context, in interface{}, next Handler) ( out interface{}, metadata Metadata, err error, ) { order := s.ids.GetOrder() var h BuildHandler = buildWrapHandler{Next: next} for i := len(order) - 1; i >= 0; i-- { h = decoratedBuildHandler{ Next: h, With: order[i].(BuildMiddleware), } } sIn := BuildInput{ Request: in, } res, metadata, err := h.HandleBuild(ctx, sIn) return res.Result, metadata, err } // Get retrieves the middleware identified by id. If the middleware is not present, returns false. func (s *BuildStep) Get(id string) (BuildMiddleware, bool) { get, ok := s.ids.Get(id) if !ok { return nil, false } return get.(BuildMiddleware), ok } // Add injects the middleware to the relative position of the middleware group. // Returns an error if the middleware already exists. func (s *BuildStep) Add(m BuildMiddleware, pos RelativePosition) error { return s.ids.Add(m, pos) } // Insert injects the middleware relative to an existing middleware id. // Returns an error if the original middleware does not exist, or the middleware // being added already exists. func (s *BuildStep) Insert(m BuildMiddleware, relativeTo string, pos RelativePosition) error { return s.ids.Insert(m, relativeTo, pos) } // Swap removes the middleware by id, replacing it with the new middleware. // Returns the middleware removed, or an error if the middleware to be removed // doesn't exist. func (s *BuildStep) Swap(id string, m BuildMiddleware) (BuildMiddleware, error) { removed, err := s.ids.Swap(id, m) if err != nil { return nil, err } return removed.(BuildMiddleware), nil } // Remove removes the middleware by id. Returns error if the middleware // doesn't exist. func (s *BuildStep) Remove(id string) (BuildMiddleware, error) { removed, err := s.ids.Remove(id) if err != nil { return nil, err } return removed.(BuildMiddleware), nil } // List returns a list of the middleware in the step. func (s *BuildStep) List() []string { return s.ids.List() } // Clear removes all middleware in the step. func (s *BuildStep) Clear() { s.ids.Clear() } type buildWrapHandler struct { Next Handler } var _ BuildHandler = (*buildWrapHandler)(nil) // Implements BuildHandler, converts types and delegates to underlying // generic handler. func (w buildWrapHandler) HandleBuild(ctx context.Context, in BuildInput) ( out BuildOutput, metadata Metadata, err error, ) { res, metadata, err := w.Next.Handle(ctx, in.Request) return BuildOutput{ Result: res, }, metadata, err } type decoratedBuildHandler struct { Next BuildHandler With BuildMiddleware } var _ BuildHandler = (*decoratedBuildHandler)(nil) func (h decoratedBuildHandler) HandleBuild(ctx context.Context, in BuildInput) ( out BuildOutput, metadata Metadata, err error, ) { return h.With.HandleBuild(ctx, in, h.Next) } // BuildHandlerFunc provides a wrapper around a function to be used as a build middleware handler. type BuildHandlerFunc func(context.Context, BuildInput) (BuildOutput, Metadata, error) // HandleBuild invokes the wrapped function with the provided arguments. func (b BuildHandlerFunc) HandleBuild(ctx context.Context, in BuildInput) (BuildOutput, Metadata, error) { return b(ctx, in) } var _ BuildHandler = BuildHandlerFunc(nil) smithy-go-1.20.3/middleware/step_deserialize.go000066400000000000000000000154741463735525100215420ustar00rootroot00000000000000package middleware import ( "context" ) // DeserializeInput provides the input parameters for the DeserializeInput to // consume. DeserializeMiddleware should not modify the Request, and instead // forward it along to the next DeserializeHandler. type DeserializeInput struct { Request interface{} } // DeserializeOutput provides the result returned by the next // DeserializeHandler. The DeserializeMiddleware should deserialize the // RawResponse into a Result that can be consumed by middleware higher up in // the stack. type DeserializeOutput struct { RawResponse interface{} Result interface{} } // DeserializeHandler provides the interface for the next handler the // DeserializeMiddleware will call in the middleware chain. type DeserializeHandler interface { HandleDeserialize(ctx context.Context, in DeserializeInput) ( out DeserializeOutput, metadata Metadata, err error, ) } // DeserializeMiddleware provides the interface for middleware specific to the // serialize step. Delegates to the next DeserializeHandler for further // processing. type DeserializeMiddleware interface { // ID returns a unique ID for the middleware in the DeserializeStep. The step does not // allow duplicate IDs. ID() string // HandleDeserialize invokes the middleware behavior which must delegate to the next handler // for the middleware chain to continue. The method must return a result or // error to its caller. HandleDeserialize(ctx context.Context, in DeserializeInput, next DeserializeHandler) ( out DeserializeOutput, metadata Metadata, err error, ) } // DeserializeMiddlewareFunc returns a DeserializeMiddleware with the unique ID // provided, and the func to be invoked. func DeserializeMiddlewareFunc(id string, fn func(context.Context, DeserializeInput, DeserializeHandler) (DeserializeOutput, Metadata, error)) DeserializeMiddleware { return deserializeMiddlewareFunc{ id: id, fn: fn, } } type deserializeMiddlewareFunc struct { // Unique ID for the middleware. id string // Middleware function to be called. fn func(context.Context, DeserializeInput, DeserializeHandler) ( DeserializeOutput, Metadata, error, ) } // ID returns the unique ID for the middleware. func (s deserializeMiddlewareFunc) ID() string { return s.id } // HandleDeserialize invokes the middleware Fn. func (s deserializeMiddlewareFunc) HandleDeserialize(ctx context.Context, in DeserializeInput, next DeserializeHandler) ( out DeserializeOutput, metadata Metadata, err error, ) { return s.fn(ctx, in, next) } var _ DeserializeMiddleware = (deserializeMiddlewareFunc{}) // DeserializeStep provides the ordered grouping of DeserializeMiddleware to be // invoked on a handler. type DeserializeStep struct { ids *orderedIDs } // NewDeserializeStep returns a DeserializeStep ready to have middleware for // initialization added to it. func NewDeserializeStep() *DeserializeStep { return &DeserializeStep{ ids: newOrderedIDs(), } } var _ Middleware = (*DeserializeStep)(nil) // ID returns the unique ID of the step as a middleware. func (s *DeserializeStep) ID() string { return "Deserialize stack step" } // HandleMiddleware invokes the middleware by decorating the next handler // provided. Returns the result of the middleware and handler being invoked. // // Implements Middleware interface. func (s *DeserializeStep) HandleMiddleware(ctx context.Context, in interface{}, next Handler) ( out interface{}, metadata Metadata, err error, ) { order := s.ids.GetOrder() var h DeserializeHandler = deserializeWrapHandler{Next: next} for i := len(order) - 1; i >= 0; i-- { h = decoratedDeserializeHandler{ Next: h, With: order[i].(DeserializeMiddleware), } } sIn := DeserializeInput{ Request: in, } res, metadata, err := h.HandleDeserialize(ctx, sIn) return res.Result, metadata, err } // Get retrieves the middleware identified by id. If the middleware is not present, returns false. func (s *DeserializeStep) Get(id string) (DeserializeMiddleware, bool) { get, ok := s.ids.Get(id) if !ok { return nil, false } return get.(DeserializeMiddleware), ok } // Add injects the middleware to the relative position of the middleware group. // Returns an error if the middleware already exists. func (s *DeserializeStep) Add(m DeserializeMiddleware, pos RelativePosition) error { return s.ids.Add(m, pos) } // Insert injects the middleware relative to an existing middleware ID. // Returns error if the original middleware does not exist, or the middleware // being added already exists. func (s *DeserializeStep) Insert(m DeserializeMiddleware, relativeTo string, pos RelativePosition) error { return s.ids.Insert(m, relativeTo, pos) } // Swap removes the middleware by id, replacing it with the new middleware. // Returns the middleware removed, or error if the middleware to be removed // doesn't exist. func (s *DeserializeStep) Swap(id string, m DeserializeMiddleware) (DeserializeMiddleware, error) { removed, err := s.ids.Swap(id, m) if err != nil { return nil, err } return removed.(DeserializeMiddleware), nil } // Remove removes the middleware by id. Returns error if the middleware // doesn't exist. func (s *DeserializeStep) Remove(id string) (DeserializeMiddleware, error) { removed, err := s.ids.Remove(id) if err != nil { return nil, err } return removed.(DeserializeMiddleware), nil } // List returns a list of the middleware in the step. func (s *DeserializeStep) List() []string { return s.ids.List() } // Clear removes all middleware in the step. func (s *DeserializeStep) Clear() { s.ids.Clear() } type deserializeWrapHandler struct { Next Handler } var _ DeserializeHandler = (*deserializeWrapHandler)(nil) // HandleDeserialize implements DeserializeHandler, converts types and delegates to underlying // generic handler. func (w deserializeWrapHandler) HandleDeserialize(ctx context.Context, in DeserializeInput) ( out DeserializeOutput, metadata Metadata, err error, ) { resp, metadata, err := w.Next.Handle(ctx, in.Request) return DeserializeOutput{ RawResponse: resp, }, metadata, err } type decoratedDeserializeHandler struct { Next DeserializeHandler With DeserializeMiddleware } var _ DeserializeHandler = (*decoratedDeserializeHandler)(nil) func (h decoratedDeserializeHandler) HandleDeserialize(ctx context.Context, in DeserializeInput) ( out DeserializeOutput, metadata Metadata, err error, ) { return h.With.HandleDeserialize(ctx, in, h.Next) } // DeserializeHandlerFunc provides a wrapper around a function to be used as a deserialize middleware handler. type DeserializeHandlerFunc func(context.Context, DeserializeInput) (DeserializeOutput, Metadata, error) // HandleDeserialize invokes the wrapped function with the given arguments. func (d DeserializeHandlerFunc) HandleDeserialize(ctx context.Context, in DeserializeInput) (DeserializeOutput, Metadata, error) { return d(ctx, in) } var _ DeserializeHandler = DeserializeHandlerFunc(nil) smithy-go-1.20.3/middleware/step_finalize.go000066400000000000000000000145161463735525100210370ustar00rootroot00000000000000package middleware import "context" // FinalizeInput provides the input parameters for the FinalizeMiddleware to // consume. FinalizeMiddleware may modify the Request value before forwarding // the FinalizeInput along to the next next FinalizeHandler. type FinalizeInput struct { Request interface{} } // FinalizeOutput provides the result returned by the next FinalizeHandler. type FinalizeOutput struct { Result interface{} } // FinalizeHandler provides the interface for the next handler the // FinalizeMiddleware will call in the middleware chain. type FinalizeHandler interface { HandleFinalize(ctx context.Context, in FinalizeInput) ( out FinalizeOutput, metadata Metadata, err error, ) } // FinalizeMiddleware provides the interface for middleware specific to the // serialize step. Delegates to the next FinalizeHandler for further // processing. type FinalizeMiddleware interface { // ID returns a unique ID for the middleware in the FinalizeStep. The step does not // allow duplicate IDs. ID() string // HandleFinalize invokes the middleware behavior which must delegate to the next handler // for the middleware chain to continue. The method must return a result or // error to its caller. HandleFinalize(ctx context.Context, in FinalizeInput, next FinalizeHandler) ( out FinalizeOutput, metadata Metadata, err error, ) } // FinalizeMiddlewareFunc returns a FinalizeMiddleware with the unique ID // provided, and the func to be invoked. func FinalizeMiddlewareFunc(id string, fn func(context.Context, FinalizeInput, FinalizeHandler) (FinalizeOutput, Metadata, error)) FinalizeMiddleware { return finalizeMiddlewareFunc{ id: id, fn: fn, } } type finalizeMiddlewareFunc struct { // Unique ID for the middleware. id string // Middleware function to be called. fn func(context.Context, FinalizeInput, FinalizeHandler) ( FinalizeOutput, Metadata, error, ) } // ID returns the unique ID for the middleware. func (s finalizeMiddlewareFunc) ID() string { return s.id } // HandleFinalize invokes the middleware Fn. func (s finalizeMiddlewareFunc) HandleFinalize(ctx context.Context, in FinalizeInput, next FinalizeHandler) ( out FinalizeOutput, metadata Metadata, err error, ) { return s.fn(ctx, in, next) } var _ FinalizeMiddleware = (finalizeMiddlewareFunc{}) // FinalizeStep provides the ordered grouping of FinalizeMiddleware to be // invoked on a handler. type FinalizeStep struct { ids *orderedIDs } // NewFinalizeStep returns a FinalizeStep ready to have middleware for // initialization added to it. func NewFinalizeStep() *FinalizeStep { return &FinalizeStep{ ids: newOrderedIDs(), } } var _ Middleware = (*FinalizeStep)(nil) // ID returns the unique id of the step as a middleware. func (s *FinalizeStep) ID() string { return "Finalize stack step" } // HandleMiddleware invokes the middleware by decorating the next handler // provided. Returns the result of the middleware and handler being invoked. // // Implements Middleware interface. func (s *FinalizeStep) HandleMiddleware(ctx context.Context, in interface{}, next Handler) ( out interface{}, metadata Metadata, err error, ) { order := s.ids.GetOrder() var h FinalizeHandler = finalizeWrapHandler{Next: next} for i := len(order) - 1; i >= 0; i-- { h = decoratedFinalizeHandler{ Next: h, With: order[i].(FinalizeMiddleware), } } sIn := FinalizeInput{ Request: in, } res, metadata, err := h.HandleFinalize(ctx, sIn) return res.Result, metadata, err } // Get retrieves the middleware identified by id. If the middleware is not present, returns false. func (s *FinalizeStep) Get(id string) (FinalizeMiddleware, bool) { get, ok := s.ids.Get(id) if !ok { return nil, false } return get.(FinalizeMiddleware), ok } // Add injects the middleware to the relative position of the middleware group. // Returns an error if the middleware already exists. func (s *FinalizeStep) Add(m FinalizeMiddleware, pos RelativePosition) error { return s.ids.Add(m, pos) } // Insert injects the middleware relative to an existing middleware ID. // Returns error if the original middleware does not exist, or the middleware // being added already exists. func (s *FinalizeStep) Insert(m FinalizeMiddleware, relativeTo string, pos RelativePosition) error { return s.ids.Insert(m, relativeTo, pos) } // Swap removes the middleware by id, replacing it with the new middleware. // Returns the middleware removed, or error if the middleware to be removed // doesn't exist. func (s *FinalizeStep) Swap(id string, m FinalizeMiddleware) (FinalizeMiddleware, error) { removed, err := s.ids.Swap(id, m) if err != nil { return nil, err } return removed.(FinalizeMiddleware), nil } // Remove removes the middleware by id. Returns error if the middleware // doesn't exist. func (s *FinalizeStep) Remove(id string) (FinalizeMiddleware, error) { removed, err := s.ids.Remove(id) if err != nil { return nil, err } return removed.(FinalizeMiddleware), nil } // List returns a list of the middleware in the step. func (s *FinalizeStep) List() []string { return s.ids.List() } // Clear removes all middleware in the step. func (s *FinalizeStep) Clear() { s.ids.Clear() } type finalizeWrapHandler struct { Next Handler } var _ FinalizeHandler = (*finalizeWrapHandler)(nil) // HandleFinalize implements FinalizeHandler, converts types and delegates to underlying // generic handler. func (w finalizeWrapHandler) HandleFinalize(ctx context.Context, in FinalizeInput) ( out FinalizeOutput, metadata Metadata, err error, ) { res, metadata, err := w.Next.Handle(ctx, in.Request) return FinalizeOutput{ Result: res, }, metadata, err } type decoratedFinalizeHandler struct { Next FinalizeHandler With FinalizeMiddleware } var _ FinalizeHandler = (*decoratedFinalizeHandler)(nil) func (h decoratedFinalizeHandler) HandleFinalize(ctx context.Context, in FinalizeInput) ( out FinalizeOutput, metadata Metadata, err error, ) { return h.With.HandleFinalize(ctx, in, h.Next) } // FinalizeHandlerFunc provides a wrapper around a function to be used as a finalize middleware handler. type FinalizeHandlerFunc func(context.Context, FinalizeInput) (FinalizeOutput, Metadata, error) // HandleFinalize invokes the wrapped function with the given arguments. func (f FinalizeHandlerFunc) HandleFinalize(ctx context.Context, in FinalizeInput) (FinalizeOutput, Metadata, error) { return f(ctx, in) } var _ FinalizeHandler = FinalizeHandlerFunc(nil) smithy-go-1.20.3/middleware/step_initialize.go000066400000000000000000000150431463735525100213730ustar00rootroot00000000000000package middleware import "context" // InitializeInput wraps the input parameters for the InitializeMiddlewares to // consume. InitializeMiddleware may modify the parameter value before // forwarding it along to the next InitializeHandler. type InitializeInput struct { Parameters interface{} } // InitializeOutput provides the result returned by the next InitializeHandler. type InitializeOutput struct { Result interface{} } // InitializeHandler provides the interface for the next handler the // InitializeMiddleware will call in the middleware chain. type InitializeHandler interface { HandleInitialize(ctx context.Context, in InitializeInput) ( out InitializeOutput, metadata Metadata, err error, ) } // InitializeMiddleware provides the interface for middleware specific to the // initialize step. Delegates to the next InitializeHandler for further // processing. type InitializeMiddleware interface { // ID returns a unique ID for the middleware in the InitializeStep. The step does not // allow duplicate IDs. ID() string // HandleInitialize invokes the middleware behavior which must delegate to the next handler // for the middleware chain to continue. The method must return a result or // error to its caller. HandleInitialize(ctx context.Context, in InitializeInput, next InitializeHandler) ( out InitializeOutput, metadata Metadata, err error, ) } // InitializeMiddlewareFunc returns a InitializeMiddleware with the unique ID provided, // and the func to be invoked. func InitializeMiddlewareFunc(id string, fn func(context.Context, InitializeInput, InitializeHandler) (InitializeOutput, Metadata, error)) InitializeMiddleware { return initializeMiddlewareFunc{ id: id, fn: fn, } } type initializeMiddlewareFunc struct { // Unique ID for the middleware. id string // Middleware function to be called. fn func(context.Context, InitializeInput, InitializeHandler) ( InitializeOutput, Metadata, error, ) } // ID returns the unique ID for the middleware. func (s initializeMiddlewareFunc) ID() string { return s.id } // HandleInitialize invokes the middleware Fn. func (s initializeMiddlewareFunc) HandleInitialize(ctx context.Context, in InitializeInput, next InitializeHandler) ( out InitializeOutput, metadata Metadata, err error, ) { return s.fn(ctx, in, next) } var _ InitializeMiddleware = (initializeMiddlewareFunc{}) // InitializeStep provides the ordered grouping of InitializeMiddleware to be // invoked on a handler. type InitializeStep struct { ids *orderedIDs } // NewInitializeStep returns an InitializeStep ready to have middleware for // initialization added to it. func NewInitializeStep() *InitializeStep { return &InitializeStep{ ids: newOrderedIDs(), } } var _ Middleware = (*InitializeStep)(nil) // ID returns the unique ID of the step as a middleware. func (s *InitializeStep) ID() string { return "Initialize stack step" } // HandleMiddleware invokes the middleware by decorating the next handler // provided. Returns the result of the middleware and handler being invoked. // // Implements Middleware interface. func (s *InitializeStep) HandleMiddleware(ctx context.Context, in interface{}, next Handler) ( out interface{}, metadata Metadata, err error, ) { order := s.ids.GetOrder() var h InitializeHandler = initializeWrapHandler{Next: next} for i := len(order) - 1; i >= 0; i-- { h = decoratedInitializeHandler{ Next: h, With: order[i].(InitializeMiddleware), } } sIn := InitializeInput{ Parameters: in, } res, metadata, err := h.HandleInitialize(ctx, sIn) return res.Result, metadata, err } // Get retrieves the middleware identified by id. If the middleware is not present, returns false. func (s *InitializeStep) Get(id string) (InitializeMiddleware, bool) { get, ok := s.ids.Get(id) if !ok { return nil, false } return get.(InitializeMiddleware), ok } // Add injects the middleware to the relative position of the middleware group. // Returns an error if the middleware already exists. func (s *InitializeStep) Add(m InitializeMiddleware, pos RelativePosition) error { return s.ids.Add(m, pos) } // Insert injects the middleware relative to an existing middleware ID. // Returns error if the original middleware does not exist, or the middleware // being added already exists. func (s *InitializeStep) Insert(m InitializeMiddleware, relativeTo string, pos RelativePosition) error { return s.ids.Insert(m, relativeTo, pos) } // Swap removes the middleware by id, replacing it with the new middleware. // Returns the middleware removed, or error if the middleware to be removed // doesn't exist. func (s *InitializeStep) Swap(id string, m InitializeMiddleware) (InitializeMiddleware, error) { removed, err := s.ids.Swap(id, m) if err != nil { return nil, err } return removed.(InitializeMiddleware), nil } // Remove removes the middleware by id. Returns error if the middleware // doesn't exist. func (s *InitializeStep) Remove(id string) (InitializeMiddleware, error) { removed, err := s.ids.Remove(id) if err != nil { return nil, err } return removed.(InitializeMiddleware), nil } // List returns a list of the middleware in the step. func (s *InitializeStep) List() []string { return s.ids.List() } // Clear removes all middleware in the step. func (s *InitializeStep) Clear() { s.ids.Clear() } type initializeWrapHandler struct { Next Handler } var _ InitializeHandler = (*initializeWrapHandler)(nil) // HandleInitialize implements InitializeHandler, converts types and delegates to underlying // generic handler. func (w initializeWrapHandler) HandleInitialize(ctx context.Context, in InitializeInput) ( out InitializeOutput, metadata Metadata, err error, ) { res, metadata, err := w.Next.Handle(ctx, in.Parameters) return InitializeOutput{ Result: res, }, metadata, err } type decoratedInitializeHandler struct { Next InitializeHandler With InitializeMiddleware } var _ InitializeHandler = (*decoratedInitializeHandler)(nil) func (h decoratedInitializeHandler) HandleInitialize(ctx context.Context, in InitializeInput) ( out InitializeOutput, metadata Metadata, err error, ) { return h.With.HandleInitialize(ctx, in, h.Next) } // InitializeHandlerFunc provides a wrapper around a function to be used as an initialize middleware handler. type InitializeHandlerFunc func(context.Context, InitializeInput) (InitializeOutput, Metadata, error) // HandleInitialize calls the wrapped function with the provided arguments. func (i InitializeHandlerFunc) HandleInitialize(ctx context.Context, in InitializeInput) (InitializeOutput, Metadata, error) { return i(ctx, in) } var _ InitializeHandler = InitializeHandlerFunc(nil) smithy-go-1.20.3/middleware/step_serialize.go000066400000000000000000000156041463735525100212240ustar00rootroot00000000000000package middleware import "context" // SerializeInput provides the input parameters for the SerializeMiddleware to // consume. SerializeMiddleware may modify the Request value before forwarding // SerializeInput along to the next SerializeHandler. The Parameters member // should not be modified by SerializeMiddleware, InitializeMiddleware should // be responsible for modifying the provided Parameter value. type SerializeInput struct { Parameters interface{} Request interface{} } // SerializeOutput provides the result returned by the next SerializeHandler. type SerializeOutput struct { Result interface{} } // SerializeHandler provides the interface for the next handler the // SerializeMiddleware will call in the middleware chain. type SerializeHandler interface { HandleSerialize(ctx context.Context, in SerializeInput) ( out SerializeOutput, metadata Metadata, err error, ) } // SerializeMiddleware provides the interface for middleware specific to the // serialize step. Delegates to the next SerializeHandler for further // processing. type SerializeMiddleware interface { // ID returns a unique ID for the middleware in the SerializeStep. The step does not // allow duplicate IDs. ID() string // HandleSerialize invokes the middleware behavior which must delegate to the next handler // for the middleware chain to continue. The method must return a result or // error to its caller. HandleSerialize(ctx context.Context, in SerializeInput, next SerializeHandler) ( out SerializeOutput, metadata Metadata, err error, ) } // SerializeMiddlewareFunc returns a SerializeMiddleware with the unique ID // provided, and the func to be invoked. func SerializeMiddlewareFunc(id string, fn func(context.Context, SerializeInput, SerializeHandler) (SerializeOutput, Metadata, error)) SerializeMiddleware { return serializeMiddlewareFunc{ id: id, fn: fn, } } type serializeMiddlewareFunc struct { // Unique ID for the middleware. id string // Middleware function to be called. fn func(context.Context, SerializeInput, SerializeHandler) ( SerializeOutput, Metadata, error, ) } // ID returns the unique ID for the middleware. func (s serializeMiddlewareFunc) ID() string { return s.id } // HandleSerialize invokes the middleware Fn. func (s serializeMiddlewareFunc) HandleSerialize(ctx context.Context, in SerializeInput, next SerializeHandler) ( out SerializeOutput, metadata Metadata, err error, ) { return s.fn(ctx, in, next) } var _ SerializeMiddleware = (serializeMiddlewareFunc{}) // SerializeStep provides the ordered grouping of SerializeMiddleware to be // invoked on a handler. type SerializeStep struct { newRequest func() interface{} ids *orderedIDs } // NewSerializeStep returns a SerializeStep ready to have middleware for // initialization added to it. The newRequest func parameter is used to // initialize the transport specific request for the stack SerializeStep to // serialize the input parameters into. func NewSerializeStep(newRequest func() interface{}) *SerializeStep { return &SerializeStep{ ids: newOrderedIDs(), newRequest: newRequest, } } var _ Middleware = (*SerializeStep)(nil) // ID returns the unique ID of the step as a middleware. func (s *SerializeStep) ID() string { return "Serialize stack step" } // HandleMiddleware invokes the middleware by decorating the next handler // provided. Returns the result of the middleware and handler being invoked. // // Implements Middleware interface. func (s *SerializeStep) HandleMiddleware(ctx context.Context, in interface{}, next Handler) ( out interface{}, metadata Metadata, err error, ) { order := s.ids.GetOrder() var h SerializeHandler = serializeWrapHandler{Next: next} for i := len(order) - 1; i >= 0; i-- { h = decoratedSerializeHandler{ Next: h, With: order[i].(SerializeMiddleware), } } sIn := SerializeInput{ Parameters: in, Request: s.newRequest(), } res, metadata, err := h.HandleSerialize(ctx, sIn) return res.Result, metadata, err } // Get retrieves the middleware identified by id. If the middleware is not present, returns false. func (s *SerializeStep) Get(id string) (SerializeMiddleware, bool) { get, ok := s.ids.Get(id) if !ok { return nil, false } return get.(SerializeMiddleware), ok } // Add injects the middleware to the relative position of the middleware group. // Returns an error if the middleware already exists. func (s *SerializeStep) Add(m SerializeMiddleware, pos RelativePosition) error { return s.ids.Add(m, pos) } // Insert injects the middleware relative to an existing middleware ID. // Returns error if the original middleware does not exist, or the middleware // being added already exists. func (s *SerializeStep) Insert(m SerializeMiddleware, relativeTo string, pos RelativePosition) error { return s.ids.Insert(m, relativeTo, pos) } // Swap removes the middleware by id, replacing it with the new middleware. // Returns the middleware removed, or error if the middleware to be removed // doesn't exist. func (s *SerializeStep) Swap(id string, m SerializeMiddleware) (SerializeMiddleware, error) { removed, err := s.ids.Swap(id, m) if err != nil { return nil, err } return removed.(SerializeMiddleware), nil } // Remove removes the middleware by id. Returns error if the middleware // doesn't exist. func (s *SerializeStep) Remove(id string) (SerializeMiddleware, error) { removed, err := s.ids.Remove(id) if err != nil { return nil, err } return removed.(SerializeMiddleware), nil } // List returns a list of the middleware in the step. func (s *SerializeStep) List() []string { return s.ids.List() } // Clear removes all middleware in the step. func (s *SerializeStep) Clear() { s.ids.Clear() } type serializeWrapHandler struct { Next Handler } var _ SerializeHandler = (*serializeWrapHandler)(nil) // Implements SerializeHandler, converts types and delegates to underlying // generic handler. func (w serializeWrapHandler) HandleSerialize(ctx context.Context, in SerializeInput) ( out SerializeOutput, metadata Metadata, err error, ) { res, metadata, err := w.Next.Handle(ctx, in.Request) return SerializeOutput{ Result: res, }, metadata, err } type decoratedSerializeHandler struct { Next SerializeHandler With SerializeMiddleware } var _ SerializeHandler = (*decoratedSerializeHandler)(nil) func (h decoratedSerializeHandler) HandleSerialize(ctx context.Context, in SerializeInput) ( out SerializeOutput, metadata Metadata, err error, ) { return h.With.HandleSerialize(ctx, in, h.Next) } // SerializeHandlerFunc provides a wrapper around a function to be used as a serialize middleware handler. type SerializeHandlerFunc func(context.Context, SerializeInput) (SerializeOutput, Metadata, error) // HandleSerialize calls the wrapped function with the provided arguments. func (s SerializeHandlerFunc) HandleSerialize(ctx context.Context, in SerializeInput) (SerializeOutput, Metadata, error) { return s(ctx, in) } var _ SerializeHandler = SerializeHandlerFunc(nil) smithy-go-1.20.3/modman.toml000066400000000000000000000003271463735525100157020ustar00rootroot00000000000000[dependencies] "github.com/jmespath/go-jmespath" = "v0.4.0" [modules] [modules.codegen] no_tag = true [modules."codegen/smithy-go-codegen/build/test-generated/go/internal/testmodule"] no_tag = true smithy-go-1.20.3/private/000077500000000000000000000000001463735525100152025ustar00rootroot00000000000000smithy-go-1.20.3/private/protocol/000077500000000000000000000000001463735525100170435ustar00rootroot00000000000000smithy-go-1.20.3/private/protocol/middleware_capture_request.go000066400000000000000000000023071463735525100250040ustar00rootroot00000000000000package protocol import ( "context" "fmt" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "net/http" "strconv" ) const captureRequestID = "CaptureProtocolTestRequest" // AddCaptureRequestMiddleware captures serialized http request during protocol test for check func AddCaptureRequestMiddleware(stack *middleware.Stack, req *http.Request) error { return stack.Build.Add(&captureRequestMiddleware{ req: req, }, middleware.After) } type captureRequestMiddleware struct { req *http.Request } func (*captureRequestMiddleware) ID() string { return captureRequestID } func (m *captureRequestMiddleware) HandleBuild(ctx context.Context, input middleware.BuildInput, next middleware.BuildHandler, ) ( output middleware.BuildOutput, metadata middleware.Metadata, err error, ) { request, ok := input.Request.(*smithyhttp.Request) if !ok { return output, metadata, fmt.Errorf("error while retrieving http request") } *m.req = *request.Build(ctx) if len(m.req.URL.RawPath) == 0 { m.req.URL.RawPath = m.req.URL.Path } if v := m.req.ContentLength; v != 0 { m.req.Header.Set("Content-Length", strconv.FormatInt(v, 10)) } return next.HandleBuild(ctx, input) } smithy-go-1.20.3/private/protocol/middleware_capture_request_test.go000066400000000000000000000061311463735525100260420ustar00rootroot00000000000000package protocol import ( "context" "github.com/aws/smithy-go/middleware" smithytesting "github.com/aws/smithy-go/testing" smithyhttp "github.com/aws/smithy-go/transport/http" "io" "io/ioutil" "net/http" "net/url" "strings" "testing" ) // TestAddCaptureRequestMiddleware tests AddCaptureRequestMiddleware func TestAddCaptureRequestMiddleware(t *testing.T) { cases := map[string]struct { Request *http.Request ExpectRequest *http.Request ExpectQuery []smithytesting.QueryItem Stream io.Reader }{ "normal request": { Request: &http.Request{ Method: "PUT", Header: map[string][]string{ "Foo": {"bar", "too"}, "Checksum": {"SHA256"}, }, URL: &url.URL{ Path: "test/path", RawQuery: "language=us®ion=us-west+east", }, ContentLength: 100, }, ExpectRequest: &http.Request{ Method: "PUT", Header: map[string][]string{ "Foo": {"bar", "too"}, "Checksum": {"SHA256"}, "Content-Length": {"100"}, }, URL: &url.URL{ Path: "test/path", RawPath: "test/path", }, Body: ioutil.NopCloser(strings.NewReader("hello world.")), }, ExpectQuery: []smithytesting.QueryItem{ { Key: "language", Value: "us", }, { Key: "region", Value: "us-west%20east", }, }, Stream: strings.NewReader("hello world."), }, } for name, c := range cases { t.Run(name, func(t *testing.T) { var err error req := &smithyhttp.Request{ Request: c.Request, } if c.Stream != nil { req, err = req.SetStream(c.Stream) if err != nil { t.Fatalf("Got error while retrieving case stream: %v", err) } } capturedRequest := &http.Request{} m := captureRequestMiddleware{ req: capturedRequest, } _, _, err = m.HandleBuild(context.Background(), middleware.BuildInput{Request: req}, middleware.BuildHandlerFunc(func(ctx context.Context, input middleware.BuildInput) ( out middleware.BuildOutput, metadata middleware.Metadata, err error) { return out, metadata, nil }), ) if err != nil { t.Fatalf("expect no error, got %v", err) } if e, a := c.ExpectRequest.Method, capturedRequest.Method; e != a { t.Errorf("expect request method %v found, got %v", e, a) } if e, a := c.ExpectRequest.URL.Path, capturedRequest.URL.RawPath; e != a { t.Errorf("expect %v path, got %v", e, a) } if c.ExpectRequest.Body != nil { expect, err := ioutil.ReadAll(c.ExpectRequest.Body) if capturedRequest.Body == nil { t.Errorf("Expect request stream %v captured, get nil", string(expect)) } actual, err := ioutil.ReadAll(capturedRequest.Body) if err != nil { t.Errorf("unable to read captured request body, %v", err) } if e, a := string(expect), string(actual); e != a { t.Errorf("expect request body to be %s, got %s", e, a) } } queryItems := smithytesting.ParseRawQuery(capturedRequest.URL.RawQuery) smithytesting.AssertHasQuery(t, c.ExpectQuery, queryItems) smithytesting.AssertHasHeader(t, c.ExpectRequest.Header, capturedRequest.Header) }) } } smithy-go-1.20.3/private/requestcompression/000077500000000000000000000000001463735525100211545ustar00rootroot00000000000000smithy-go-1.20.3/private/requestcompression/gzip.go000066400000000000000000000012621463735525100224550ustar00rootroot00000000000000package requestcompression import ( "bytes" "compress/gzip" "fmt" "io" ) func gzipCompress(input io.Reader) ([]byte, error) { var b bytes.Buffer w, err := gzip.NewWriterLevel(&b, gzip.DefaultCompression) if err != nil { return nil, fmt.Errorf("failed to create gzip writer, %v", err) } inBytes, err := io.ReadAll(input) if err != nil { return nil, fmt.Errorf("failed read payload to compress, %v", err) } if _, err = w.Write(inBytes); err != nil { return nil, fmt.Errorf("failed to write payload to be compressed, %v", err) } if err = w.Close(); err != nil { return nil, fmt.Errorf("failed to flush payload being compressed, %v", err) } return b.Bytes(), nil } smithy-go-1.20.3/private/requestcompression/middleware_capture_request_compression.go000066400000000000000000000031711463735525100315360ustar00rootroot00000000000000package requestcompression import ( "bytes" "context" "fmt" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "io" "net/http" ) const captureUncompressedRequestID = "CaptureUncompressedRequest" // AddCaptureUncompressedRequestMiddleware captures http request before compress encoding for check func AddCaptureUncompressedRequestMiddleware(stack *middleware.Stack, buf *bytes.Buffer) error { return stack.Serialize.Insert(&captureUncompressedRequestMiddleware{ buf: buf, }, "RequestCompression", middleware.Before) } type captureUncompressedRequestMiddleware struct { req *http.Request buf *bytes.Buffer bytes []byte } // ID returns id of the captureUncompressedRequestMiddleware func (*captureUncompressedRequestMiddleware) ID() string { return captureUncompressedRequestID } // HandleSerialize captures request payload before it is compressed by request compression middleware func (m *captureUncompressedRequestMiddleware) HandleSerialize(ctx context.Context, input middleware.SerializeInput, next middleware.SerializeHandler, ) ( output middleware.SerializeOutput, metadata middleware.Metadata, err error, ) { request, ok := input.Request.(*smithyhttp.Request) if !ok { return output, metadata, fmt.Errorf("error when retrieving http request") } _, err = io.Copy(m.buf, request.GetStream()) if err != nil { return output, metadata, fmt.Errorf("error when copying http request stream: %q", err) } if err = request.RewindStream(); err != nil { return output, metadata, fmt.Errorf("error when rewinding request stream: %q", err) } return next.HandleSerialize(ctx, input) } smithy-go-1.20.3/private/requestcompression/request_compression.go000066400000000000000000000064021463735525100256160ustar00rootroot00000000000000// Package requestcompression implements runtime support for smithy-modeled // request compression. // // This package is designated as private and is intended for use only by the // smithy client runtime. The exported API therein is not considered stable and // is subject to breaking changes without notice. package requestcompression import ( "bytes" "context" "fmt" "github.com/aws/smithy-go/middleware" "github.com/aws/smithy-go/transport/http" "io" ) const MaxRequestMinCompressSizeBytes = 10485760 // Enumeration values for supported compress Algorithms. const ( GZIP = "gzip" ) type compressFunc func(io.Reader) ([]byte, error) var allowedAlgorithms = map[string]compressFunc{ GZIP: gzipCompress, } // AddRequestCompression add requestCompression middleware to op stack func AddRequestCompression(stack *middleware.Stack, disabled bool, minBytes int64, algorithms []string) error { return stack.Serialize.Add(&requestCompression{ disableRequestCompression: disabled, requestMinCompressSizeBytes: minBytes, compressAlgorithms: algorithms, }, middleware.After) } type requestCompression struct { disableRequestCompression bool requestMinCompressSizeBytes int64 compressAlgorithms []string } // ID returns the ID of the middleware func (m requestCompression) ID() string { return "RequestCompression" } // HandleSerialize gzip compress the request's stream/body if enabled by config fields func (m requestCompression) HandleSerialize( ctx context.Context, in middleware.SerializeInput, next middleware.SerializeHandler, ) ( out middleware.SerializeOutput, metadata middleware.Metadata, err error, ) { if m.disableRequestCompression { return next.HandleSerialize(ctx, in) } // still need to check requestMinCompressSizeBytes in case it is out of range after service client config if m.requestMinCompressSizeBytes < 0 || m.requestMinCompressSizeBytes > MaxRequestMinCompressSizeBytes { return out, metadata, fmt.Errorf("invalid range for min request compression size bytes %d, must be within 0 and 10485760 inclusively", m.requestMinCompressSizeBytes) } req, ok := in.Request.(*http.Request) if !ok { return out, metadata, fmt.Errorf("unknown request type %T", req) } for _, algorithm := range m.compressAlgorithms { compressFunc := allowedAlgorithms[algorithm] if compressFunc != nil { if stream := req.GetStream(); stream != nil { size, found, err := req.StreamLength() if err != nil { return out, metadata, fmt.Errorf("error while finding request stream length, %v", err) } else if !found || size < m.requestMinCompressSizeBytes { return next.HandleSerialize(ctx, in) } compressedBytes, err := compressFunc(stream) if err != nil { return out, metadata, fmt.Errorf("failed to compress request stream, %v", err) } var newReq *http.Request if newReq, err = req.SetStream(bytes.NewReader(compressedBytes)); err != nil { return out, metadata, fmt.Errorf("failed to set request stream, %v", err) } *req = *newReq if val := req.Header.Get("Content-Encoding"); val != "" { req.Header.Set("Content-Encoding", fmt.Sprintf("%s, %s", val, algorithm)) } else { req.Header.Set("Content-Encoding", algorithm) } } break } } return next.HandleSerialize(ctx, in) } smithy-go-1.20.3/private/requestcompression/request_compression_test.go000066400000000000000000000074471463735525100266670ustar00rootroot00000000000000package requestcompression import ( "bytes" "compress/gzip" "context" "fmt" "github.com/aws/smithy-go/middleware" "github.com/aws/smithy-go/transport/http" "io" "reflect" "strings" "testing" ) func TestRequestCompression(t *testing.T) { cases := map[string]struct { DisableRequestCompression bool RequestMinCompressSizeBytes int64 ContentLength int64 Header map[string][]string Stream io.Reader ExpectedStream []byte ExpectedHeader map[string][]string }{ "GZip request stream": { Stream: strings.NewReader("Hi, world!"), ExpectedStream: []byte("Hi, world!"), ExpectedHeader: map[string][]string{ "Content-Encoding": {"gzip"}, }, }, "GZip request stream with existing encoding header": { Stream: strings.NewReader("Hi, world!"), ExpectedStream: []byte("Hi, world!"), Header: map[string][]string{ "Content-Encoding": {"custom"}, }, ExpectedHeader: map[string][]string{ "Content-Encoding": {"custom, gzip"}, }, }, "GZip request stream smaller than min compress request size": { RequestMinCompressSizeBytes: 100, Stream: strings.NewReader("Hi, world!"), ExpectedStream: []byte("Hi, world!"), ExpectedHeader: map[string][]string{}, }, "Disable GZip request stream": { DisableRequestCompression: true, Stream: strings.NewReader("Hi, world!"), ExpectedStream: []byte("Hi, world!"), ExpectedHeader: map[string][]string{}, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { var err error req := http.NewStackRequest().(*http.Request) req.ContentLength = c.ContentLength req, _ = req.SetStream(c.Stream) if c.Header != nil { req.Header = c.Header } var updatedRequest *http.Request m := requestCompression{ disableRequestCompression: c.DisableRequestCompression, requestMinCompressSizeBytes: c.RequestMinCompressSizeBytes, compressAlgorithms: []string{GZIP}, } _, _, err = m.HandleSerialize(context.Background(), middleware.SerializeInput{Request: req}, middleware.SerializeHandlerFunc(func(ctx context.Context, input middleware.SerializeInput) ( out middleware.SerializeOutput, metadata middleware.Metadata, err error) { updatedRequest = input.Request.(*http.Request) return out, metadata, nil }), ) if err != nil { t.Fatalf("expect no error, got %v", err) } if stream := updatedRequest.GetStream(); stream != nil { if err := testUnzipContent(stream, c.ExpectedStream, c.DisableRequestCompression, c.RequestMinCompressSizeBytes); err != nil { t.Errorf("error while checking request stream: %q", err) } } if e, a := c.ExpectedHeader, map[string][]string(updatedRequest.Header); !reflect.DeepEqual(e, a) { t.Errorf("expect request header to be %q, got %q", e, a) } }) } } func testUnzipContent(content io.Reader, expect []byte, disableRequestCompression bool, requestMinCompressionSizeBytes int64) error { if disableRequestCompression || int64(len(expect)) < requestMinCompressionSizeBytes { b, err := io.ReadAll(content) if err != nil { return fmt.Errorf("error while reading request") } if e, a := expect, b; !bytes.Equal(e, a) { return fmt.Errorf("expect content to be %s, got %s", e, a) } } else { r, err := gzip.NewReader(content) if err != nil { return fmt.Errorf("error while reading request") } var actualBytes bytes.Buffer _, err = actualBytes.ReadFrom(r) if err != nil { return fmt.Errorf("error while unzipping request payload") } if e, a := expect, actualBytes.Bytes(); !bytes.Equal(e, a) { return fmt.Errorf("expect unzipped content to be %s, got %s", e, a) } } return nil } smithy-go-1.20.3/properties.go000066400000000000000000000031251463735525100162540ustar00rootroot00000000000000package smithy // PropertiesReader provides an interface for reading metadata from the // underlying metadata container. type PropertiesReader interface { Get(key interface{}) interface{} } // Properties provides storing and reading metadata values. Keys may be any // comparable value type. Get and Set will panic if a key is not comparable. // // The zero value for a Properties instance is ready for reads/writes without // any additional initialization. type Properties struct { values map[interface{}]interface{} } // Get attempts to retrieve the value the key points to. Returns nil if the // key was not found. // // Panics if key type is not comparable. func (m *Properties) Get(key interface{}) interface{} { m.lazyInit() return m.values[key] } // Set stores the value pointed to by the key. If a value already exists at // that key it will be replaced with the new value. // // Panics if the key type is not comparable. func (m *Properties) Set(key, value interface{}) { m.lazyInit() m.values[key] = value } // Has returns whether the key exists in the metadata. // // Panics if the key type is not comparable. func (m *Properties) Has(key interface{}) bool { m.lazyInit() _, ok := m.values[key] return ok } // SetAll accepts all of the given Properties into the receiver, overwriting // any existing keys in the case of conflicts. func (m *Properties) SetAll(other *Properties) { if other.values == nil { return } m.lazyInit() for k, v := range other.values { m.values[k] = v } } func (m *Properties) lazyInit() { if m.values == nil { m.values = map[interface{}]interface{}{} } } smithy-go-1.20.3/properties_test.go000066400000000000000000000010041463735525100173050ustar00rootroot00000000000000package smithy import "testing" func TestProperties(t *testing.T) { original := map[interface{}]interface{}{ "abc": 123, "efg": "hij", } var m Properties for k, v := range original { m.Set(k, v) } for k, v := range original { if m.Get(k) != v { t.Errorf("expect key / value properties to be equivalent: %v / %v", k, v) } } var n Properties n.SetAll(&m) for k, v := range original { if n.Get(k) != v { t.Errorf("expect key / value properties to be equivalent: %v / %v", k, v) } } }smithy-go-1.20.3/ptr/000077500000000000000000000000001463735525100143355ustar00rootroot00000000000000smithy-go-1.20.3/ptr/doc.go000066400000000000000000000003011463735525100154230ustar00rootroot00000000000000// Package ptr provides utilities for converting scalar literal type values to and from pointers inline. package ptr //go:generate go run -tags codegen generate.go //go:generate gofmt -w -s . smithy-go-1.20.3/ptr/from_ptr.go000066400000000000000000000344311463735525100165210ustar00rootroot00000000000000// Code generated by smithy-go/ptr/generate.go DO NOT EDIT. package ptr import ( "time" ) // ToBool returns bool value dereferenced if the passed // in pointer was not nil. Returns a bool zero value if the // pointer was nil. func ToBool(p *bool) (v bool) { if p == nil { return v } return *p } // ToBoolSlice returns a slice of bool values, that are // dereferenced if the passed in pointer was not nil. Returns a bool // zero value if the pointer was nil. func ToBoolSlice(vs []*bool) []bool { ps := make([]bool, len(vs)) for i, v := range vs { ps[i] = ToBool(v) } return ps } // ToBoolMap returns a map of bool values, that are // dereferenced if the passed in pointer was not nil. The bool // zero value is used if the pointer was nil. func ToBoolMap(vs map[string]*bool) map[string]bool { ps := make(map[string]bool, len(vs)) for k, v := range vs { ps[k] = ToBool(v) } return ps } // ToByte returns byte value dereferenced if the passed // in pointer was not nil. Returns a byte zero value if the // pointer was nil. func ToByte(p *byte) (v byte) { if p == nil { return v } return *p } // ToByteSlice returns a slice of byte values, that are // dereferenced if the passed in pointer was not nil. Returns a byte // zero value if the pointer was nil. func ToByteSlice(vs []*byte) []byte { ps := make([]byte, len(vs)) for i, v := range vs { ps[i] = ToByte(v) } return ps } // ToByteMap returns a map of byte values, that are // dereferenced if the passed in pointer was not nil. The byte // zero value is used if the pointer was nil. func ToByteMap(vs map[string]*byte) map[string]byte { ps := make(map[string]byte, len(vs)) for k, v := range vs { ps[k] = ToByte(v) } return ps } // ToString returns string value dereferenced if the passed // in pointer was not nil. Returns a string zero value if the // pointer was nil. func ToString(p *string) (v string) { if p == nil { return v } return *p } // ToStringSlice returns a slice of string values, that are // dereferenced if the passed in pointer was not nil. Returns a string // zero value if the pointer was nil. func ToStringSlice(vs []*string) []string { ps := make([]string, len(vs)) for i, v := range vs { ps[i] = ToString(v) } return ps } // ToStringMap returns a map of string values, that are // dereferenced if the passed in pointer was not nil. The string // zero value is used if the pointer was nil. func ToStringMap(vs map[string]*string) map[string]string { ps := make(map[string]string, len(vs)) for k, v := range vs { ps[k] = ToString(v) } return ps } // ToInt returns int value dereferenced if the passed // in pointer was not nil. Returns a int zero value if the // pointer was nil. func ToInt(p *int) (v int) { if p == nil { return v } return *p } // ToIntSlice returns a slice of int values, that are // dereferenced if the passed in pointer was not nil. Returns a int // zero value if the pointer was nil. func ToIntSlice(vs []*int) []int { ps := make([]int, len(vs)) for i, v := range vs { ps[i] = ToInt(v) } return ps } // ToIntMap returns a map of int values, that are // dereferenced if the passed in pointer was not nil. The int // zero value is used if the pointer was nil. func ToIntMap(vs map[string]*int) map[string]int { ps := make(map[string]int, len(vs)) for k, v := range vs { ps[k] = ToInt(v) } return ps } // ToInt8 returns int8 value dereferenced if the passed // in pointer was not nil. Returns a int8 zero value if the // pointer was nil. func ToInt8(p *int8) (v int8) { if p == nil { return v } return *p } // ToInt8Slice returns a slice of int8 values, that are // dereferenced if the passed in pointer was not nil. Returns a int8 // zero value if the pointer was nil. func ToInt8Slice(vs []*int8) []int8 { ps := make([]int8, len(vs)) for i, v := range vs { ps[i] = ToInt8(v) } return ps } // ToInt8Map returns a map of int8 values, that are // dereferenced if the passed in pointer was not nil. The int8 // zero value is used if the pointer was nil. func ToInt8Map(vs map[string]*int8) map[string]int8 { ps := make(map[string]int8, len(vs)) for k, v := range vs { ps[k] = ToInt8(v) } return ps } // ToInt16 returns int16 value dereferenced if the passed // in pointer was not nil. Returns a int16 zero value if the // pointer was nil. func ToInt16(p *int16) (v int16) { if p == nil { return v } return *p } // ToInt16Slice returns a slice of int16 values, that are // dereferenced if the passed in pointer was not nil. Returns a int16 // zero value if the pointer was nil. func ToInt16Slice(vs []*int16) []int16 { ps := make([]int16, len(vs)) for i, v := range vs { ps[i] = ToInt16(v) } return ps } // ToInt16Map returns a map of int16 values, that are // dereferenced if the passed in pointer was not nil. The int16 // zero value is used if the pointer was nil. func ToInt16Map(vs map[string]*int16) map[string]int16 { ps := make(map[string]int16, len(vs)) for k, v := range vs { ps[k] = ToInt16(v) } return ps } // ToInt32 returns int32 value dereferenced if the passed // in pointer was not nil. Returns a int32 zero value if the // pointer was nil. func ToInt32(p *int32) (v int32) { if p == nil { return v } return *p } // ToInt32Slice returns a slice of int32 values, that are // dereferenced if the passed in pointer was not nil. Returns a int32 // zero value if the pointer was nil. func ToInt32Slice(vs []*int32) []int32 { ps := make([]int32, len(vs)) for i, v := range vs { ps[i] = ToInt32(v) } return ps } // ToInt32Map returns a map of int32 values, that are // dereferenced if the passed in pointer was not nil. The int32 // zero value is used if the pointer was nil. func ToInt32Map(vs map[string]*int32) map[string]int32 { ps := make(map[string]int32, len(vs)) for k, v := range vs { ps[k] = ToInt32(v) } return ps } // ToInt64 returns int64 value dereferenced if the passed // in pointer was not nil. Returns a int64 zero value if the // pointer was nil. func ToInt64(p *int64) (v int64) { if p == nil { return v } return *p } // ToInt64Slice returns a slice of int64 values, that are // dereferenced if the passed in pointer was not nil. Returns a int64 // zero value if the pointer was nil. func ToInt64Slice(vs []*int64) []int64 { ps := make([]int64, len(vs)) for i, v := range vs { ps[i] = ToInt64(v) } return ps } // ToInt64Map returns a map of int64 values, that are // dereferenced if the passed in pointer was not nil. The int64 // zero value is used if the pointer was nil. func ToInt64Map(vs map[string]*int64) map[string]int64 { ps := make(map[string]int64, len(vs)) for k, v := range vs { ps[k] = ToInt64(v) } return ps } // ToUint returns uint value dereferenced if the passed // in pointer was not nil. Returns a uint zero value if the // pointer was nil. func ToUint(p *uint) (v uint) { if p == nil { return v } return *p } // ToUintSlice returns a slice of uint values, that are // dereferenced if the passed in pointer was not nil. Returns a uint // zero value if the pointer was nil. func ToUintSlice(vs []*uint) []uint { ps := make([]uint, len(vs)) for i, v := range vs { ps[i] = ToUint(v) } return ps } // ToUintMap returns a map of uint values, that are // dereferenced if the passed in pointer was not nil. The uint // zero value is used if the pointer was nil. func ToUintMap(vs map[string]*uint) map[string]uint { ps := make(map[string]uint, len(vs)) for k, v := range vs { ps[k] = ToUint(v) } return ps } // ToUint8 returns uint8 value dereferenced if the passed // in pointer was not nil. Returns a uint8 zero value if the // pointer was nil. func ToUint8(p *uint8) (v uint8) { if p == nil { return v } return *p } // ToUint8Slice returns a slice of uint8 values, that are // dereferenced if the passed in pointer was not nil. Returns a uint8 // zero value if the pointer was nil. func ToUint8Slice(vs []*uint8) []uint8 { ps := make([]uint8, len(vs)) for i, v := range vs { ps[i] = ToUint8(v) } return ps } // ToUint8Map returns a map of uint8 values, that are // dereferenced if the passed in pointer was not nil. The uint8 // zero value is used if the pointer was nil. func ToUint8Map(vs map[string]*uint8) map[string]uint8 { ps := make(map[string]uint8, len(vs)) for k, v := range vs { ps[k] = ToUint8(v) } return ps } // ToUint16 returns uint16 value dereferenced if the passed // in pointer was not nil. Returns a uint16 zero value if the // pointer was nil. func ToUint16(p *uint16) (v uint16) { if p == nil { return v } return *p } // ToUint16Slice returns a slice of uint16 values, that are // dereferenced if the passed in pointer was not nil. Returns a uint16 // zero value if the pointer was nil. func ToUint16Slice(vs []*uint16) []uint16 { ps := make([]uint16, len(vs)) for i, v := range vs { ps[i] = ToUint16(v) } return ps } // ToUint16Map returns a map of uint16 values, that are // dereferenced if the passed in pointer was not nil. The uint16 // zero value is used if the pointer was nil. func ToUint16Map(vs map[string]*uint16) map[string]uint16 { ps := make(map[string]uint16, len(vs)) for k, v := range vs { ps[k] = ToUint16(v) } return ps } // ToUint32 returns uint32 value dereferenced if the passed // in pointer was not nil. Returns a uint32 zero value if the // pointer was nil. func ToUint32(p *uint32) (v uint32) { if p == nil { return v } return *p } // ToUint32Slice returns a slice of uint32 values, that are // dereferenced if the passed in pointer was not nil. Returns a uint32 // zero value if the pointer was nil. func ToUint32Slice(vs []*uint32) []uint32 { ps := make([]uint32, len(vs)) for i, v := range vs { ps[i] = ToUint32(v) } return ps } // ToUint32Map returns a map of uint32 values, that are // dereferenced if the passed in pointer was not nil. The uint32 // zero value is used if the pointer was nil. func ToUint32Map(vs map[string]*uint32) map[string]uint32 { ps := make(map[string]uint32, len(vs)) for k, v := range vs { ps[k] = ToUint32(v) } return ps } // ToUint64 returns uint64 value dereferenced if the passed // in pointer was not nil. Returns a uint64 zero value if the // pointer was nil. func ToUint64(p *uint64) (v uint64) { if p == nil { return v } return *p } // ToUint64Slice returns a slice of uint64 values, that are // dereferenced if the passed in pointer was not nil. Returns a uint64 // zero value if the pointer was nil. func ToUint64Slice(vs []*uint64) []uint64 { ps := make([]uint64, len(vs)) for i, v := range vs { ps[i] = ToUint64(v) } return ps } // ToUint64Map returns a map of uint64 values, that are // dereferenced if the passed in pointer was not nil. The uint64 // zero value is used if the pointer was nil. func ToUint64Map(vs map[string]*uint64) map[string]uint64 { ps := make(map[string]uint64, len(vs)) for k, v := range vs { ps[k] = ToUint64(v) } return ps } // ToFloat32 returns float32 value dereferenced if the passed // in pointer was not nil. Returns a float32 zero value if the // pointer was nil. func ToFloat32(p *float32) (v float32) { if p == nil { return v } return *p } // ToFloat32Slice returns a slice of float32 values, that are // dereferenced if the passed in pointer was not nil. Returns a float32 // zero value if the pointer was nil. func ToFloat32Slice(vs []*float32) []float32 { ps := make([]float32, len(vs)) for i, v := range vs { ps[i] = ToFloat32(v) } return ps } // ToFloat32Map returns a map of float32 values, that are // dereferenced if the passed in pointer was not nil. The float32 // zero value is used if the pointer was nil. func ToFloat32Map(vs map[string]*float32) map[string]float32 { ps := make(map[string]float32, len(vs)) for k, v := range vs { ps[k] = ToFloat32(v) } return ps } // ToFloat64 returns float64 value dereferenced if the passed // in pointer was not nil. Returns a float64 zero value if the // pointer was nil. func ToFloat64(p *float64) (v float64) { if p == nil { return v } return *p } // ToFloat64Slice returns a slice of float64 values, that are // dereferenced if the passed in pointer was not nil. Returns a float64 // zero value if the pointer was nil. func ToFloat64Slice(vs []*float64) []float64 { ps := make([]float64, len(vs)) for i, v := range vs { ps[i] = ToFloat64(v) } return ps } // ToFloat64Map returns a map of float64 values, that are // dereferenced if the passed in pointer was not nil. The float64 // zero value is used if the pointer was nil. func ToFloat64Map(vs map[string]*float64) map[string]float64 { ps := make(map[string]float64, len(vs)) for k, v := range vs { ps[k] = ToFloat64(v) } return ps } // ToTime returns time.Time value dereferenced if the passed // in pointer was not nil. Returns a time.Time zero value if the // pointer was nil. func ToTime(p *time.Time) (v time.Time) { if p == nil { return v } return *p } // ToTimeSlice returns a slice of time.Time values, that are // dereferenced if the passed in pointer was not nil. Returns a time.Time // zero value if the pointer was nil. func ToTimeSlice(vs []*time.Time) []time.Time { ps := make([]time.Time, len(vs)) for i, v := range vs { ps[i] = ToTime(v) } return ps } // ToTimeMap returns a map of time.Time values, that are // dereferenced if the passed in pointer was not nil. The time.Time // zero value is used if the pointer was nil. func ToTimeMap(vs map[string]*time.Time) map[string]time.Time { ps := make(map[string]time.Time, len(vs)) for k, v := range vs { ps[k] = ToTime(v) } return ps } // ToDuration returns time.Duration value dereferenced if the passed // in pointer was not nil. Returns a time.Duration zero value if the // pointer was nil. func ToDuration(p *time.Duration) (v time.Duration) { if p == nil { return v } return *p } // ToDurationSlice returns a slice of time.Duration values, that are // dereferenced if the passed in pointer was not nil. Returns a time.Duration // zero value if the pointer was nil. func ToDurationSlice(vs []*time.Duration) []time.Duration { ps := make([]time.Duration, len(vs)) for i, v := range vs { ps[i] = ToDuration(v) } return ps } // ToDurationMap returns a map of time.Duration values, that are // dereferenced if the passed in pointer was not nil. The time.Duration // zero value is used if the pointer was nil. func ToDurationMap(vs map[string]*time.Duration) map[string]time.Duration { ps := make(map[string]time.Duration, len(vs)) for k, v := range vs { ps[k] = ToDuration(v) } return ps } smithy-go-1.20.3/ptr/gen_scalars.go000066400000000000000000000031211463735525100171420ustar00rootroot00000000000000//go:build codegen // +build codegen package ptr import "strings" func GetScalars() Scalars { return Scalars{ {Type: "bool"}, {Type: "byte"}, {Type: "string"}, {Type: "int"}, {Type: "int8"}, {Type: "int16"}, {Type: "int32"}, {Type: "int64"}, {Type: "uint"}, {Type: "uint8"}, {Type: "uint16"}, {Type: "uint32"}, {Type: "uint64"}, {Type: "float32"}, {Type: "float64"}, {Type: "Time", Import: &Import{Path: "time"}}, {Type: "Duration", Import: &Import{Path: "time"}}, } } // Import provides the import path and optional alias type Import struct { Path string Alias string } // Package returns the Go package name for the import. Returns alias if set. func (i Import) Package() string { if v := i.Alias; len(v) != 0 { return v } if v := i.Path; len(v) != 0 { parts := strings.Split(v, "/") pkg := parts[len(parts)-1] return pkg } return "" } // Scalar provides the definition of a type to generate pointer utilities for. type Scalar struct { Type string Import *Import } // Name returns the exported function name for the type. func (t Scalar) Name() string { return strings.Title(t.Type) } // Symbol returns the scalar's Go symbol with path if needed. func (t Scalar) Symbol() string { if t.Import != nil { return t.Import.Package() + "." + t.Type } return t.Type } // Scalars is a list of scalars. type Scalars []Scalar // Imports returns all imports for the scalars. func (ts Scalars) Imports() []*Import { imports := []*Import{} for _, t := range ts { if v := t.Import; v != nil { imports = append(imports, v) } } return imports } smithy-go-1.20.3/ptr/generate.go000066400000000000000000000070361463735525100164640ustar00rootroot00000000000000//go:build ignore // +build ignore package main import ( "fmt" "log" "os" "text/template" "github.com/aws/smithy-go/ptr" ) func main() { types := ptr.GetScalars() for filename, tmplName := range map[string]string{ "to_ptr.go": "scalar to pointer", "from_ptr.go": "scalar from pointer", } { if err := generateFile(filename, tmplName, types); err != nil { log.Fatalf("%s file generation failed, %v", filename, err) } } } func generateFile(filename string, tmplName string, types ptr.Scalars) (err error) { f, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create %s file, %v", filename, err) } defer func() { closeErr := f.Close() if err == nil { err = closeErr } else if closeErr != nil { err = fmt.Errorf("close error: %v, original error: %w", closeErr, err) } }() if err = ptrTmpl.ExecuteTemplate(f, tmplName, types); err != nil { return fmt.Errorf("failed to generate %s file, %v", filename, err) } return nil } var ptrTmpl = template.Must(template.New("ptrTmpl").Parse(` {{- define "header" }} // Code generated by smithy-go/ptr/generate.go DO NOT EDIT. package ptr import ( {{- range $_, $import := $.Imports }} "{{ $import.Path }}" {{- end }} ) {{- end }} {{- define "scalar from pointer" }} {{ template "header" $ }} {{ range $_, $type := $ }} {{ template "from pointer func" $type }} {{ template "from pointers func" $type }} {{- end }} {{- end }} {{- define "scalar to pointer" }} {{ template "header" $ }} {{ range $_, $type := $ }} {{ template "to pointer func" $type }} {{ template "to pointers func" $type }} {{- end }} {{- end }} {{- define "to pointer func" }} // {{ $.Name }} returns a pointer value for the {{ $.Symbol }} value passed in. func {{ $.Name }}(v {{ $.Symbol }}) *{{ $.Symbol }} { return &v } {{- end }} {{- define "to pointers func" }} // {{ $.Name }}Slice returns a slice of {{ $.Symbol }} pointers from the values // passed in. func {{ $.Name }}Slice(vs []{{ $.Symbol }}) []*{{ $.Symbol }} { ps := make([]*{{ $.Symbol }}, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // {{ $.Name }}Map returns a map of {{ $.Symbol }} pointers from the values // passed in. func {{ $.Name }}Map(vs map[string]{{ $.Symbol }}) map[string]*{{ $.Symbol }} { ps := make(map[string]*{{ $.Symbol }}, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } {{- end }} {{- define "from pointer func" }} // To{{ $.Name }} returns {{ $.Symbol }} value dereferenced if the passed // in pointer was not nil. Returns a {{ $.Symbol }} zero value if the // pointer was nil. func To{{ $.Name }}(p *{{ $.Symbol }}) (v {{ $.Symbol }}) { if p == nil { return v } return *p } {{- end }} {{- define "from pointers func" }} // To{{ $.Name }}Slice returns a slice of {{ $.Symbol }} values, that are // dereferenced if the passed in pointer was not nil. Returns a {{ $.Symbol }} // zero value if the pointer was nil. func To{{ $.Name }}Slice(vs []*{{ $.Symbol }}) []{{ $.Symbol }} { ps := make([]{{ $.Symbol }}, len(vs)) for i, v := range vs { ps[i] = To{{ $.Name }}(v) } return ps } // To{{ $.Name }}Map returns a map of {{ $.Symbol }} values, that are // dereferenced if the passed in pointer was not nil. The {{ $.Symbol }} // zero value is used if the pointer was nil. func To{{ $.Name }}Map(vs map[string]*{{ $.Symbol }}) map[string]{{ $.Symbol }} { ps := make(map[string]{{ $.Symbol }}, len(vs)) for k, v := range vs { ps[k] = To{{ $.Name }}(v) } return ps } {{- end }} `)) smithy-go-1.20.3/ptr/to_ptr.go000066400000000000000000000227721463735525100162050ustar00rootroot00000000000000// Code generated by smithy-go/ptr/generate.go DO NOT EDIT. package ptr import ( "time" ) // Bool returns a pointer value for the bool value passed in. func Bool(v bool) *bool { return &v } // BoolSlice returns a slice of bool pointers from the values // passed in. func BoolSlice(vs []bool) []*bool { ps := make([]*bool, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // BoolMap returns a map of bool pointers from the values // passed in. func BoolMap(vs map[string]bool) map[string]*bool { ps := make(map[string]*bool, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Byte returns a pointer value for the byte value passed in. func Byte(v byte) *byte { return &v } // ByteSlice returns a slice of byte pointers from the values // passed in. func ByteSlice(vs []byte) []*byte { ps := make([]*byte, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // ByteMap returns a map of byte pointers from the values // passed in. func ByteMap(vs map[string]byte) map[string]*byte { ps := make(map[string]*byte, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // String returns a pointer value for the string value passed in. func String(v string) *string { return &v } // StringSlice returns a slice of string pointers from the values // passed in. func StringSlice(vs []string) []*string { ps := make([]*string, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // StringMap returns a map of string pointers from the values // passed in. func StringMap(vs map[string]string) map[string]*string { ps := make(map[string]*string, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Int returns a pointer value for the int value passed in. func Int(v int) *int { return &v } // IntSlice returns a slice of int pointers from the values // passed in. func IntSlice(vs []int) []*int { ps := make([]*int, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // IntMap returns a map of int pointers from the values // passed in. func IntMap(vs map[string]int) map[string]*int { ps := make(map[string]*int, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Int8 returns a pointer value for the int8 value passed in. func Int8(v int8) *int8 { return &v } // Int8Slice returns a slice of int8 pointers from the values // passed in. func Int8Slice(vs []int8) []*int8 { ps := make([]*int8, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // Int8Map returns a map of int8 pointers from the values // passed in. func Int8Map(vs map[string]int8) map[string]*int8 { ps := make(map[string]*int8, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Int16 returns a pointer value for the int16 value passed in. func Int16(v int16) *int16 { return &v } // Int16Slice returns a slice of int16 pointers from the values // passed in. func Int16Slice(vs []int16) []*int16 { ps := make([]*int16, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // Int16Map returns a map of int16 pointers from the values // passed in. func Int16Map(vs map[string]int16) map[string]*int16 { ps := make(map[string]*int16, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Int32 returns a pointer value for the int32 value passed in. func Int32(v int32) *int32 { return &v } // Int32Slice returns a slice of int32 pointers from the values // passed in. func Int32Slice(vs []int32) []*int32 { ps := make([]*int32, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // Int32Map returns a map of int32 pointers from the values // passed in. func Int32Map(vs map[string]int32) map[string]*int32 { ps := make(map[string]*int32, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Int64 returns a pointer value for the int64 value passed in. func Int64(v int64) *int64 { return &v } // Int64Slice returns a slice of int64 pointers from the values // passed in. func Int64Slice(vs []int64) []*int64 { ps := make([]*int64, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // Int64Map returns a map of int64 pointers from the values // passed in. func Int64Map(vs map[string]int64) map[string]*int64 { ps := make(map[string]*int64, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Uint returns a pointer value for the uint value passed in. func Uint(v uint) *uint { return &v } // UintSlice returns a slice of uint pointers from the values // passed in. func UintSlice(vs []uint) []*uint { ps := make([]*uint, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // UintMap returns a map of uint pointers from the values // passed in. func UintMap(vs map[string]uint) map[string]*uint { ps := make(map[string]*uint, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Uint8 returns a pointer value for the uint8 value passed in. func Uint8(v uint8) *uint8 { return &v } // Uint8Slice returns a slice of uint8 pointers from the values // passed in. func Uint8Slice(vs []uint8) []*uint8 { ps := make([]*uint8, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // Uint8Map returns a map of uint8 pointers from the values // passed in. func Uint8Map(vs map[string]uint8) map[string]*uint8 { ps := make(map[string]*uint8, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Uint16 returns a pointer value for the uint16 value passed in. func Uint16(v uint16) *uint16 { return &v } // Uint16Slice returns a slice of uint16 pointers from the values // passed in. func Uint16Slice(vs []uint16) []*uint16 { ps := make([]*uint16, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // Uint16Map returns a map of uint16 pointers from the values // passed in. func Uint16Map(vs map[string]uint16) map[string]*uint16 { ps := make(map[string]*uint16, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Uint32 returns a pointer value for the uint32 value passed in. func Uint32(v uint32) *uint32 { return &v } // Uint32Slice returns a slice of uint32 pointers from the values // passed in. func Uint32Slice(vs []uint32) []*uint32 { ps := make([]*uint32, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // Uint32Map returns a map of uint32 pointers from the values // passed in. func Uint32Map(vs map[string]uint32) map[string]*uint32 { ps := make(map[string]*uint32, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Uint64 returns a pointer value for the uint64 value passed in. func Uint64(v uint64) *uint64 { return &v } // Uint64Slice returns a slice of uint64 pointers from the values // passed in. func Uint64Slice(vs []uint64) []*uint64 { ps := make([]*uint64, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // Uint64Map returns a map of uint64 pointers from the values // passed in. func Uint64Map(vs map[string]uint64) map[string]*uint64 { ps := make(map[string]*uint64, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Float32 returns a pointer value for the float32 value passed in. func Float32(v float32) *float32 { return &v } // Float32Slice returns a slice of float32 pointers from the values // passed in. func Float32Slice(vs []float32) []*float32 { ps := make([]*float32, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // Float32Map returns a map of float32 pointers from the values // passed in. func Float32Map(vs map[string]float32) map[string]*float32 { ps := make(map[string]*float32, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Float64 returns a pointer value for the float64 value passed in. func Float64(v float64) *float64 { return &v } // Float64Slice returns a slice of float64 pointers from the values // passed in. func Float64Slice(vs []float64) []*float64 { ps := make([]*float64, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // Float64Map returns a map of float64 pointers from the values // passed in. func Float64Map(vs map[string]float64) map[string]*float64 { ps := make(map[string]*float64, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Time returns a pointer value for the time.Time value passed in. func Time(v time.Time) *time.Time { return &v } // TimeSlice returns a slice of time.Time pointers from the values // passed in. func TimeSlice(vs []time.Time) []*time.Time { ps := make([]*time.Time, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // TimeMap returns a map of time.Time pointers from the values // passed in. func TimeMap(vs map[string]time.Time) map[string]*time.Time { ps := make(map[string]*time.Time, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } // Duration returns a pointer value for the time.Duration value passed in. func Duration(v time.Duration) *time.Duration { return &v } // DurationSlice returns a slice of time.Duration pointers from the values // passed in. func DurationSlice(vs []time.Duration) []*time.Duration { ps := make([]*time.Duration, len(vs)) for i, v := range vs { vv := v ps[i] = &vv } return ps } // DurationMap returns a map of time.Duration pointers from the values // passed in. func DurationMap(vs map[string]time.Duration) map[string]*time.Duration { ps := make(map[string]*time.Duration, len(vs)) for k, v := range vs { vv := v ps[k] = &vv } return ps } smithy-go-1.20.3/ptr/to_ptr_test.go000066400000000000000000000335371463735525100172450ustar00rootroot00000000000000package ptr import ( "testing" "time" ) func TestBool(t *testing.T) { var v *bool v = Bool(true) if !*v { t.Errorf("expected %t, but received %t", true, *v) } v = Bool(false) if *v { t.Errorf("expected %t, but received %t", false, *v) } } func TestBoolSlice(t *testing.T) { s := []bool{true, false} ps := BoolSlice(s) if len(ps) != 2 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if !*ps[0] { t.Errorf("expected %t, but received %t", true, *ps[0]) } if *ps[1] { t.Errorf("expected %t, but received %t", false, *ps[1]) } } func TestBoolMap(t *testing.T) { s := map[string]bool{ "true": true, "false": false, } ps := BoolMap(s) if len(ps) != 2 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if !*ps["true"] { t.Errorf("expected %t, but received %t", true, *ps["true"]) } if *ps["false"] { t.Errorf("expected %t, but received %t", false, *ps["false"]) } } func TestByte(t *testing.T) { v := Byte(42) if *v != 42 { t.Errorf("expected %d, but received %d", 42, *v) } } func TestByteSlice(t *testing.T) { s := []byte{1, 1, 2, 3, 5, 8, 13, 21} ps := ByteSlice(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 8, len(ps)) } if *ps[0] != 1 { t.Errorf("expected %d, but received %d", 1, *ps[0]) } if *ps[7] != 21 { t.Errorf("expected %d, but received %d", 21, *ps[7]) } } func TestByteMap(t *testing.T) { s := map[string]byte{ "F0": 1, "F1": 1, "F2": 2, "F3": 3, "F4": 5, "F5": 8, "F6": 13, "F7": 21, } ps := ByteMap(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if *ps["F0"] != 1 { t.Errorf("expected %d, but received %d", 1, *ps["F0"]) } if *ps["F7"] != 21 { t.Errorf("expected %d, but received %d", 21, *ps["F7"]) } } func TestString(t *testing.T) { v := String("foo") if *v != "foo" { t.Errorf("expected %q, but received %q", "foo", *v) } } func TestStringSlice(t *testing.T) { s := []string{"foo", "bar", "fizz", "buzz", "hoge", "fuga"} ps := StringSlice(s) if len(ps) != 6 { t.Errorf("expected %d, but received %d", 6, len(ps)) } if *ps[0] != "foo" { t.Errorf("expected %q, but received %q", "foo", *ps[0]) } if *ps[5] != "fuga" { t.Errorf("expected %q, but received %q", "fuga", *ps[5]) } } func TestStringMap(t *testing.T) { s := map[string]string{ "foo": "bar", "fizz": "buzz", "hoge": "fuga", } ps := StringMap(s) if len(ps) != 3 { t.Errorf("expected %d, but received %d", 3, len(ps)) } if *ps["foo"] != "bar" { t.Errorf("expected %q, but received %q", "foo", *ps["foo"]) } if *ps["hoge"] != "fuga" { t.Errorf("expected %q, but received %q", "fuga", *ps["hoge"]) } } func TestInt(t *testing.T) { v := Int(42) if *v != 42 { t.Errorf("expected %d, but received %d", 42, *v) } } func TestIntSlice(t *testing.T) { s := []int{1, 1, 2, 3, 5, 8, 13, 21} ps := IntSlice(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 8, len(ps)) } if *ps[0] != 1 { t.Errorf("expected %d, but received %d", 1, *ps[0]) } if *ps[7] != 21 { t.Errorf("expected %d, but received %d", 21, *ps[7]) } } func TestIntMap(t *testing.T) { s := map[string]int{ "F0": 1, "F1": 1, "F2": 2, "F3": 3, "F4": 5, "F5": 8, "F6": 13, "F7": 21, } ps := IntMap(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if *ps["F0"] != 1 { t.Errorf("expected %d, but received %d", 1, *ps["F0"]) } if *ps["F7"] != 21 { t.Errorf("expected %d, but received %d", 21, *ps["F7"]) } } func TestInt8(t *testing.T) { v := Int8(42) if *v != 42 { t.Errorf("expected %d, but received %d", 42, *v) } } func TestInt8Slice(t *testing.T) { s := []int8{1, 1, 2, 3, 5, 8, 13, 21} ps := Int8Slice(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 8, len(ps)) } if *ps[0] != 1 { t.Errorf("expected %d, but received %d", 1, *ps[0]) } if *ps[7] != 21 { t.Errorf("expected %d, but received %d", 21, *ps[7]) } } func TestInt8Map(t *testing.T) { s := map[string]int8{ "F0": 1, "F1": 1, "F2": 2, "F3": 3, "F4": 5, "F5": 8, "F6": 13, "F7": 21, } ps := Int8Map(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if *ps["F0"] != 1 { t.Errorf("expected %d, but received %d", 1, *ps["F0"]) } if *ps["F7"] != 21 { t.Errorf("expected %d, but received %d", 21, *ps["F7"]) } } func TestInt16(t *testing.T) { v := Int16(42) if *v != 42 { t.Errorf("expected %d, but received %d", 42, *v) } } func TestIntSlice16(t *testing.T) { s := []int16{1, 1, 2, 3, 5, 8, 13, 21} ps := Int16Slice(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 8, len(ps)) } if *ps[0] != 1 { t.Errorf("expected %d, but received %d", 1, *ps[0]) } if *ps[7] != 21 { t.Errorf("expected %d, but received %d", 21, *ps[7]) } } func TestInt16Map(t *testing.T) { s := map[string]int16{ "F0": 1, "F1": 1, "F2": 2, "F3": 3, "F4": 5, "F5": 8, "F6": 13, "F7": 21, } ps := Int16Map(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if *ps["F0"] != 1 { t.Errorf("expected %d, but received %d", 1, *ps["F0"]) } if *ps["F7"] != 21 { t.Errorf("expected %d, but received %d", 21, *ps["F7"]) } } func TestInt32(t *testing.T) { v := Int32(42) if *v != 42 { t.Errorf("expected %d, but received %d", 42, *v) } } func TestInt32Slice(t *testing.T) { s := []int32{1, 1, 2, 3, 5, 8, 13, 21} ps := Int32Slice(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 8, len(ps)) } if *ps[0] != 1 { t.Errorf("expected %d, but received %d", 1, *ps[0]) } if *ps[7] != 21 { t.Errorf("expected %d, but received %d", 21, *ps[7]) } } func TestInt32Map(t *testing.T) { s := map[string]int32{ "F0": 1, "F1": 1, "F2": 2, "F3": 3, "F4": 5, "F5": 8, "F6": 13, "F7": 21, } ps := Int32Map(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if *ps["F0"] != 1 { t.Errorf("expected %d, but received %d", 1, *ps["F0"]) } if *ps["F7"] != 21 { t.Errorf("expected %d, but received %d", 21, *ps["F7"]) } } func TestInt64(t *testing.T) { v := Int64(42) if *v != 42 { t.Errorf("expected %d, but received %d", 42, *v) } } func TestInt64Slice(t *testing.T) { s := []int64{1, 1, 2, 3, 5, 8, 13, 21} ps := Int64Slice(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 8, len(ps)) } if *ps[0] != 1 { t.Errorf("expected %d, but received %d", 1, *ps[0]) } if *ps[7] != 21 { t.Errorf("expected %d, but received %d", 21, *ps[7]) } } func TestInt64Map(t *testing.T) { s := map[string]int64{ "F0": 1, "F1": 1, "F2": 2, "F3": 3, "F4": 5, "F5": 8, "F6": 13, "F7": 21, } ps := Int64Map(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if *ps["F0"] != 1 { t.Errorf("expected %d, but received %d", 1, *ps["F0"]) } if *ps["F7"] != 21 { t.Errorf("expected %d, but received %d", 21, *ps["F7"]) } } func TestUint(t *testing.T) { v := Uint(42) if *v != 42 { t.Errorf("expected %d, but received %d", 42, *v) } } func TestUintSlice(t *testing.T) { s := []uint{1, 1, 2, 3, 5, 8, 13, 21} ps := UintSlice(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 8, len(ps)) } if *ps[0] != 1 { t.Errorf("expected %d, but received %d", 1, *ps[0]) } if *ps[7] != 21 { t.Errorf("expected %d, but received %d", 21, *ps[7]) } } func TestUintMap(t *testing.T) { s := map[string]uint{ "F0": 1, "F1": 1, "F2": 2, "F3": 3, "F4": 5, "F5": 8, "F6": 13, "F7": 21, } ps := UintMap(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if *ps["F0"] != 1 { t.Errorf("expected %d, but received %d", 1, *ps["F0"]) } if *ps["F7"] != 21 { t.Errorf("expected %d, but received %d", 21, *ps["F7"]) } } func TestUint8(t *testing.T) { v := Uint8(42) if *v != 42 { t.Errorf("expected %d, but received %d", 42, *v) } } func TestUint8Slice(t *testing.T) { s := []uint8{1, 1, 2, 3, 5, 8, 13, 21} ps := Uint8Slice(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 8, len(ps)) } if *ps[0] != 1 { t.Errorf("expected %d, but received %d", 1, *ps[0]) } if *ps[7] != 21 { t.Errorf("expected %d, but received %d", 21, *ps[7]) } } func TestUint8Map(t *testing.T) { s := map[string]uint8{ "F0": 1, "F1": 1, "F2": 2, "F3": 3, "F4": 5, "F5": 8, "F6": 13, "F7": 21, } ps := Uint8Map(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if *ps["F0"] != 1 { t.Errorf("expected %d, but received %d", 1, *ps["F0"]) } if *ps["F7"] != 21 { t.Errorf("expected %d, but received %d", 21, *ps["F7"]) } } func TestUint16(t *testing.T) { v := Uint16(42) if *v != 42 { t.Errorf("expected %d, but received %d", 42, *v) } } func TestUintSlice16(t *testing.T) { s := []uint16{1, 1, 2, 3, 5, 8, 13, 21} ps := Uint16Slice(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 8, len(ps)) } if *ps[0] != 1 { t.Errorf("expected %d, but received %d", 1, *ps[0]) } if *ps[7] != 21 { t.Errorf("expected %d, but received %d", 21, *ps[7]) } } func TestUint16Map(t *testing.T) { s := map[string]uint{ "F0": 1, "F1": 1, "F2": 2, "F3": 3, "F4": 5, "F5": 8, "F6": 13, "F7": 21, } ps := UintMap(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if *ps["F0"] != 1 { t.Errorf("expected %d, but received %d", 1, *ps["F0"]) } if *ps["F7"] != 21 { t.Errorf("expected %d, but received %d", 21, *ps["F7"]) } } func TestUint32(t *testing.T) { v := Uint32(42) if *v != 42 { t.Errorf("expected %d, but received %d", 42, *v) } } func TestUint32Slice(t *testing.T) { s := []uint32{1, 1, 2, 3, 5, 8, 13, 21} ps := Uint32Slice(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 8, len(ps)) } if *ps[0] != 1 { t.Errorf("expected %d, but received %d", 1, *ps[0]) } if *ps[7] != 21 { t.Errorf("expected %d, but received %d", 21, *ps[7]) } } func TestUint32Map(t *testing.T) { s := map[string]uint32{ "F0": 1, "F1": 1, "F2": 2, "F3": 3, "F4": 5, "F5": 8, "F6": 13, "F7": 21, } ps := Uint32Map(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if *ps["F0"] != 1 { t.Errorf("expected %d, but received %d", 1, *ps["F0"]) } if *ps["F7"] != 21 { t.Errorf("expected %d, but received %d", 21, *ps["F7"]) } } func TestUint64(t *testing.T) { v := Uint64(42) if *v != 42 { t.Errorf("expected %d, but received %d", 42, *v) } } func TestUint64Slice(t *testing.T) { s := []uint64{1, 1, 2, 3, 5, 8, 13, 21} ps := Uint64Slice(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 8, len(ps)) } if *ps[0] != 1 { t.Errorf("expected %d, but received %d", 1, *ps[0]) } if *ps[7] != 21 { t.Errorf("expected %d, but received %d", 21, *ps[7]) } } func TestUint64Map(t *testing.T) { s := map[string]uint64{ "F0": 1, "F1": 1, "F2": 2, "F3": 3, "F4": 5, "F5": 8, "F6": 13, "F7": 21, } ps := Uint64Map(s) if len(ps) != 8 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if *ps["F0"] != 1 { t.Errorf("expected %d, but received %d", 1, *ps["F0"]) } if *ps["F7"] != 21 { t.Errorf("expected %d, but received %d", 21, *ps["F7"]) } } func TestFloat32(t *testing.T) { v := Float32(0.5) if *v != 0.5 { t.Errorf("expected %f, but received %f", 0.5, *v) } } func TestFloat32Slice(t *testing.T) { s := []float32{0.5, 0.25, 0.125, 0.0625} ps := Float32Slice(s) if len(ps) != 4 { t.Errorf("expected %d, but received %d", 4, len(ps)) } if *ps[0] != 0.5 { t.Errorf("expected %f, but received %f", 0.5, *ps[0]) } if *ps[3] != 0.0625 { t.Errorf("expected %f, but received %f", 0.0625, *ps[7]) } } func TestFloat32Map(t *testing.T) { s := map[string]float32{ "F0": 0.5, "F1": 0.25, "F2": 0.125, "F3": 0.0625, } ps := Float32Map(s) if len(ps) != 4 { t.Errorf("expected %d, but received %d", 4, len(ps)) } if *ps["F0"] != 0.5 { t.Errorf("expected %f, but received %f", 0.5, *ps["F0"]) } if *ps["F3"] != 0.0625 { t.Errorf("expected %f, but received %f", 0.0625, *ps["F3"]) } } func TestFloat64(t *testing.T) { v := Float64(0.5) if *v != 0.5 { t.Errorf("expected %f, but received %f", 0.5, *v) } } func TestFloat64Slice(t *testing.T) { s := []float64{0.5, 0.25, 0.125, 0.0625} ps := Float64Slice(s) if len(ps) != 4 { t.Errorf("expected %d, but received %d", 4, len(ps)) } if *ps[0] != 0.5 { t.Errorf("expected %f, but received %f", 0.5, *ps[0]) } if *ps[3] != 0.0625 { t.Errorf("expected %f, but received %f", 0.0625, *ps[7]) } } func TestFloat64Map(t *testing.T) { s := map[string]float64{ "F0": 0.5, "F1": 0.25, "F2": 0.125, "F3": 0.0625, } ps := Float64Map(s) if len(ps) != 4 { t.Errorf("expected %d, but received %d", 4, len(ps)) } if *ps["F0"] != 0.5 { t.Errorf("expected %f, but received %f", 0.5, *ps["F0"]) } if *ps["F3"] != 0.0625 { t.Errorf("expected %f, but received %f", 0.0625, *ps["F3"]) } } func TestTime(t *testing.T) { v := Time(time.Unix(1234567890, 0)) if v.Unix() != 1234567890 { t.Errorf("expected %d, but received %d", 1234567890, v.Unix()) } } func TestTimeSlice(t *testing.T) { s := []time.Time{time.Unix(1234567890, 0), time.Unix(2147483647, 0)} ps := TimeSlice(s) if len(ps) != 2 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if ps[0].Unix() != 1234567890 { t.Errorf("expected %d, but received %d", 1234567890, ps[0].Unix()) } if ps[1].Unix() != 2147483647 { t.Errorf("expected %d, but received %d", 2147483647, ps[1].Unix()) } } func TestTimeMap(t *testing.T) { s := map[string]time.Time{ "2009-02-13T23:31:30Z": time.Unix(1234567890, 0), "2038-01-19T03:14:07Z": time.Unix(2147483647, 0), } ps := TimeMap(s) if len(ps) != 2 { t.Errorf("expected %d, but received %d", 2, len(ps)) } if ps["2009-02-13T23:31:30Z"].Unix() != 1234567890 { t.Errorf("expected %d, but received %d", 1234567890, ps["2009-02-13T23:31:30Z"].Unix()) } if ps["2038-01-19T03:14:07Z"].Unix() != 2147483647 { t.Errorf("expected %d, but received %d", 2147483647, ps["2038-01-19T03:14:07Z"].Unix()) } } smithy-go-1.20.3/rand/000077500000000000000000000000001463735525100144545ustar00rootroot00000000000000smithy-go-1.20.3/rand/doc.go000066400000000000000000000001521463735525100155460ustar00rootroot00000000000000// Package rand provides utilities for creating and working with random value // generators. package rand smithy-go-1.20.3/rand/rand.go000066400000000000000000000012621463735525100157300ustar00rootroot00000000000000package rand import ( "crypto/rand" "fmt" "io" "math/big" ) func init() { Reader = rand.Reader } // Reader provides a random reader that can reset during testing. var Reader io.Reader // Int63n returns a int64 between zero and value of max, read from an io.Reader source. func Int63n(reader io.Reader, max int64) (int64, error) { bi, err := rand.Int(reader, big.NewInt(max)) if err != nil { return 0, fmt.Errorf("failed to read random value, %w", err) } return bi.Int64(), nil } // CryptoRandInt63n returns a random int64 between zero and value of max // obtained from the crypto rand source. func CryptoRandInt63n(max int64) (int64, error) { return Int63n(Reader, max) } smithy-go-1.20.3/rand/uuid.go000066400000000000000000000046761463735525100157660ustar00rootroot00000000000000package rand import ( "encoding/hex" "io" ) const dash byte = '-' // UUIDIdempotencyToken provides a utility to get idempotency tokens in the // UUID format. type UUIDIdempotencyToken struct { uuid *UUID } // NewUUIDIdempotencyToken returns a idempotency token provider returning // tokens in the UUID random format using the reader provided. func NewUUIDIdempotencyToken(r io.Reader) *UUIDIdempotencyToken { return &UUIDIdempotencyToken{uuid: NewUUID(r)} } // GetIdempotencyToken returns a random UUID value for Idempotency token. func (u UUIDIdempotencyToken) GetIdempotencyToken() (string, error) { return u.uuid.GetUUID() } // UUID provides computing random UUID version 4 values from a random source // reader. type UUID struct { randSrc io.Reader } // NewUUID returns an initialized UUID value that can be used to retrieve // random UUID version 4 values. func NewUUID(r io.Reader) *UUID { return &UUID{randSrc: r} } // GetUUID returns a random UUID version 4 string representation sourced from the random reader the // UUID was created with. Returns an error if unable to compute the UUID. func (r *UUID) GetUUID() (string, error) { var b [16]byte if _, err := io.ReadFull(r.randSrc, b[:]); err != nil { return "", err } r.makeUUIDv4(b[:]) return format(b), nil } // GetBytes returns a byte slice containing a random UUID version 4 sourced from the random reader the // UUID was created with. Returns an error if unable to compute the UUID. func (r *UUID) GetBytes() (u []byte, err error) { u = make([]byte, 16) if _, err = io.ReadFull(r.randSrc, u); err != nil { return u, err } r.makeUUIDv4(u) return u, nil } func (r *UUID) makeUUIDv4(u []byte) { // 13th character is "4" u[6] = (u[6] & 0x0f) | 0x40 // Version 4 // 17th character is "8", "9", "a", or "b" u[8] = (u[8] & 0x3f) | 0x80 // Variant most significant bits are 10x where x can be either 1 or 0 } // Format returns the canonical text representation of a UUID. // This implementation is optimized to not use fmt. // Example: 82e42f16-b6cc-4d5b-95f5-d403c4befd3d func format(u [16]byte) string { // https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29 var scratch [36]byte hex.Encode(scratch[:8], u[0:4]) scratch[8] = dash hex.Encode(scratch[9:13], u[4:6]) scratch[13] = dash hex.Encode(scratch[14:18], u[6:8]) scratch[18] = dash hex.Encode(scratch[19:23], u[8:10]) scratch[23] = dash hex.Encode(scratch[24:], u[10:]) return string(scratch[:]) } smithy-go-1.20.3/rand/uuid_test.go000066400000000000000000000016521463735525100170140ustar00rootroot00000000000000package rand_test import ( "bytes" mathrand "math/rand" "testing" "time" "github.com/aws/smithy-go/rand" ) func TestUUID(t *testing.T) { randSrc := make([]byte, 32) for i := 16; i < len(randSrc); i++ { randSrc[i] = 1 } uuid := rand.NewUUID(bytes.NewReader(randSrc)) v, err := uuid.GetUUID() if err != nil { t.Fatalf("expect no error getting zero UUID, got %v", err) } if e, a := `00000000-0000-4000-8000-000000000000`, v; e != a { t.Errorf("expect %v, got %v", e, a) } v, err = uuid.GetUUID() if err != nil { t.Fatalf("expect no error getting ones UUID, got %v", err) } if e, a := `01010101-0101-4101-8101-010101010101`, v; e != a { t.Errorf("expect %v, got %v", e, a) } } func BenchmarkUUID_GetUUID(b *testing.B) { src := mathrand.NewSource(time.Now().Unix()) uuid := rand.NewUUID(mathrand.New(src)) for i := 0; i < b.N; i++ { _, err := uuid.GetUUID() if err != nil { b.Fatal(err) } } } smithy-go-1.20.3/sync/000077500000000000000000000000001463735525100145045ustar00rootroot00000000000000smithy-go-1.20.3/sync/error.go000066400000000000000000000020501463735525100161610ustar00rootroot00000000000000package sync import "sync" // OnceErr wraps the behavior of recording an error // once and signal on a channel when this has occurred. // Signaling is done by closing of the channel. // // Type is safe for concurrent usage. type OnceErr struct { mu sync.RWMutex err error ch chan struct{} } // NewOnceErr return a new OnceErr func NewOnceErr() *OnceErr { return &OnceErr{ ch: make(chan struct{}, 1), } } // Err acquires a read-lock and returns an // error if one has been set. func (e *OnceErr) Err() error { e.mu.RLock() err := e.err e.mu.RUnlock() return err } // SetError acquires a write-lock and will set // the underlying error value if one has not been set. func (e *OnceErr) SetError(err error) { if err == nil { return } e.mu.Lock() if e.err == nil { e.err = err close(e.ch) } e.mu.Unlock() } // ErrorSet returns a channel that will be used to signal // that an error has been set. This channel will be closed // when the error value has been set for OnceErr. func (e *OnceErr) ErrorSet() <-chan struct{} { return e.ch } smithy-go-1.20.3/testing/000077500000000000000000000000001463735525100152055ustar00rootroot00000000000000smithy-go-1.20.3/testing/bytes.go000066400000000000000000000067621463735525100166750ustar00rootroot00000000000000package testing import ( "bytes" "encoding/hex" "fmt" "io" "io/ioutil" ) // Enumeration values for supported compress Algorithms. const ( GZIP = "gzip" ) type compareCompressFunc func([]byte, io.Reader) error var allowedAlgorithms = map[string]compareCompressFunc{ GZIP: GzipCompareCompressBytes, } // CompareReaderEmpty checks if the reader is nil, or contains no bytes. // Returns an error if not empty. func CompareReaderEmpty(r io.Reader) error { if r == nil { return nil } b, err := ioutil.ReadAll(r) if err != nil && err != io.EOF { return fmt.Errorf("unable to read from reader, %v", err) } if len(b) != 0 { return fmt.Errorf("reader not empty, got\n%v", hex.Dump(b)) } return nil } // CompareReaderBytes compares the reader with the expected bytes. Returns an // error if the two bytes are not equal. func CompareReaderBytes(r io.Reader, expect []byte) error { if r == nil { return fmt.Errorf("missing body") } actual, err := ioutil.ReadAll(r) if err != nil { return fmt.Errorf("unable to read, %v", err) } if !bytes.Equal(expect, actual) { return fmt.Errorf("bytes not equal\nexpect:\n%v\nactual:\n%v", hex.Dump(expect), hex.Dump(actual)) } return nil } // CompareJSONReaderBytes compares the reader containing serialized JSON // document. Deserializes the JSON documents to determine if they are equal. // Return an error if the two JSON documents are not equal. func CompareJSONReaderBytes(r io.Reader, expect []byte) error { if r == nil { return fmt.Errorf("missing body") } actual, err := ioutil.ReadAll(r) if err != nil { return fmt.Errorf("unable to read, %v", err) } if err := JSONEqual(expect, actual); err != nil { return fmt.Errorf("JSON documents not equal, %v", err) } return nil } // CompareXMLReaderBytes compares the reader with expected xml byte func CompareXMLReaderBytes(r io.Reader, expect []byte) error { if r == nil { return fmt.Errorf("missing body") } actual, err := ioutil.ReadAll(r) if err != nil { return err } if err := XMLEqual(expect, actual); err != nil { return fmt.Errorf("XML documents not equal, %w", err) } return nil } // CompareURLFormReaderBytes compares the reader containing serialized URLForm // document. Deserializes the URLForm documents to determine if they are equal. // Return an error if the two URLForm documents are not equal. func CompareURLFormReaderBytes(r io.Reader, expect []byte) error { if r == nil { return fmt.Errorf("missing body") } actual, err := ioutil.ReadAll(r) if err != nil { return fmt.Errorf("unable to read, %v", err) } if err := URLFormEqual(expect, actual); err != nil { return fmt.Errorf("URL query forms not equal, %v", err) } return nil } // CompareCompressedBytes compares the request stream before and after possible request compression func CompareCompressedBytes(expect *bytes.Buffer, actual io.Reader, disable bool, min int64, algorithm string) error { expectBytes := expect.Bytes() if disable || int64(len(expectBytes)) < min { actualBytes, err := io.ReadAll(actual) if err != nil { return fmt.Errorf("error while reading request: %q", err) } if e, a := expectBytes, actualBytes; !bytes.Equal(e, a) { return fmt.Errorf("expect content to be %s, got %s", e, a) } } else { compareFn := allowedAlgorithms[algorithm] if compareFn == nil { return fmt.Errorf("compress algorithm %s is not allowed", algorithm) } if err := compareFn(expectBytes, actual); err != nil { return fmt.Errorf("error while comparing unzipped content: %q", err) } } return nil } smithy-go-1.20.3/testing/cbor.go000066400000000000000000000076411463735525100164710ustar00rootroot00000000000000package testing import ( "encoding/base64" "fmt" "io" "math" "reflect" "github.com/aws/smithy-go/encoding/cbor" ) // CompareCBOR checks whether two CBOR values are equivalent. // // The function signature is tailored for use in smithy protocol tests, where // the expected encoding is given in base64, and the actual value to check is // passed from the mock HTTP request body. func CompareCBOR(actual io.Reader, expect64 string) error { ap, err := io.ReadAll(actual) if err != nil { return fmt.Errorf("read actual: %w", err) } av, err := cbor.Decode(ap) if err != nil { return fmt.Errorf("decode actual: %w", err) } ep, err := base64.StdEncoding.DecodeString(expect64) if err != nil { return fmt.Errorf("decode expect64: %w", err) } ev, err := cbor.Decode(ep) if err != nil { return fmt.Errorf("decode expect: %w", err) } return cmpCBOR(ev, av, "") } func cmpCBOR(e, a cbor.Value, path string) error { switch v := e.(type) { case cbor.Uint, cbor.NegInt, cbor.Slice, cbor.String, cbor.Bool, *cbor.Nil, *cbor.Undefined: if !reflect.DeepEqual(e, a) { return fmt.Errorf("%s: %v != %v", path, e, a) } return nil case cbor.List: return cmpList(v, a, path) case cbor.Map: return cmpMap(v, a, path) case *cbor.Tag: return cmpTag(v, a, path) case cbor.Float32: return cmpF32(v, a, path) case cbor.Float64: return cmpF64(v, a, path) default: return fmt.Errorf("%s: unrecognized variant %T", path, e) } } func cmpList(e cbor.List, a cbor.Value, path string) error { av, ok := a.(cbor.List) if !ok { return fmt.Errorf("%s: %T != %T", path, e, a) } if len(e) != len(av) { return fmt.Errorf("%s: length %d != %d", path, len(e), len(av)) } for i := 0; i < len(e); i++ { ipath := fmt.Sprintf("%s[%d]", path, i) if err := cmpCBOR(e[i], av[i], ipath); err != nil { return err } } return nil } func cmpMap(e cbor.Map, a cbor.Value, path string) error { av, ok := a.(cbor.Map) if !ok { return fmt.Errorf("%s: %T != %T", path, e, a) } if len(e) != len(av) { return fmt.Errorf("%s: length %d != %d", path, len(e), len(av)) } for k, ev := range e { avv, ok := av[k] if !ok { return fmt.Errorf("%s: missing key %s", path, k) } kpath := fmt.Sprintf("%s[%q]", path, k) if err := cmpCBOR(ev, avv, kpath); err != nil { return err } } return nil } func cmpTag(e *cbor.Tag, a cbor.Value, path string) error { av, ok := a.(*cbor.Tag) if !ok { return fmt.Errorf("%s: %T != %T", path, e, a) } if e.ID != av.ID { return fmt.Errorf("%s: tag ID %d != %d", path, e.ID, av.ID) } return cmpCBOR(e.Value, av.Value, path) } func cmpF32(e cbor.Float32, a cbor.Value, path string) error { av, ok := a.(cbor.Float32) if !ok { return fmt.Errorf("%s: %T != %T", path, e, a) } ebits, abits := math.Float32bits(float32(e)), math.Float32bits(float32(av)) if enan, anan := isNaN32(ebits), isNaN32(abits); enan || anan { if enan != anan { return fmt.Errorf("%s: NaN: float32(%x) != float32(%x)", path, ebits, abits) } return nil } if ebits != abits { return fmt.Errorf("%s: float32(%x) != float32(%x)", path, ebits, abits) } return nil } func cmpF64(e cbor.Float64, a cbor.Value, path string) error { av, ok := a.(cbor.Float64) if !ok { return fmt.Errorf("%s: %T != %T", path, e, a) } ebits, abits := math.Float64bits(float64(e)), math.Float64bits(float64(av)) if enan, anan := isNaN64(ebits), isNaN64(abits); enan || anan { if enan != anan { return fmt.Errorf("%s: NaN: float64(%x) != float64(%x)", path, ebits, abits) } return nil } if math.Float64bits(float64(e)) != math.Float64bits(float64(av)) { return fmt.Errorf("%s: float64(%x) != float64(%x)", path, ebits, abits) } return nil } func isNaN32(f uint32) bool { const infmask = 0x7f800000 return f&infmask == infmask && f != infmask && f != (1<<31)|infmask } func isNaN64(f uint64) bool { const infmask = 0x7ff00000_00000000 return f&infmask == infmask && f != infmask && f != (1<<63)|infmask } smithy-go-1.20.3/testing/doc.go000066400000000000000000000004441463735525100163030ustar00rootroot00000000000000// Package testing provides utilities for testing smith clients and protocols. package testing // T provides the testing interface for capturing failures with testing assert // utilities. type T interface { Error(args ...interface{}) Errorf(format string, args ...interface{}) Helper() } smithy-go-1.20.3/testing/document.go000066400000000000000000000065211463735525100173560ustar00rootroot00000000000000package testing import ( "bytes" "encoding/json" "fmt" "reflect" "sort" "strings" "github.com/aws/smithy-go/testing/xml" ) // JSONEqual compares two JSON documents and identifies if the documents contain // the same values. Returns an error if the two documents are not equal. func JSONEqual(expectBytes, actualBytes []byte) error { var expect interface{} if err := json.Unmarshal(expectBytes, &expect); err != nil { return fmt.Errorf("failed to unmarshal expected bytes, %v", err) } var actual interface{} if err := json.Unmarshal(actualBytes, &actual); err != nil { return fmt.Errorf("failed to unmarshal actual bytes, %v", err) } if !reflect.DeepEqual(expect, actual) { return fmt.Errorf("JSON mismatch: %v != %v", expect, actual) } return nil } // AssertJSONEqual compares two JSON documents and identifies if the documents // contain the same values. Emits a testing error, and returns false if the // documents are not equal. func AssertJSONEqual(t T, expect, actual []byte) bool { t.Helper() if err := JSONEqual(expect, actual); err != nil { t.Errorf("expect JSON documents to be equal, %v", err) return false } return true } // XMLEqual asserts two xml documents by sorting the XML and comparing the strings // It returns an error in case of mismatch or in case of malformed xml found while sorting. // In case of mismatched XML, the error string will contain the diff between the two XMLs. func XMLEqual(expectBytes, actualBytes []byte) error { actualString, err := xml.SortXML(bytes.NewBuffer(actualBytes), true) if err != nil { return err } expectedString, err := xml.SortXML(bytes.NewBuffer(expectBytes), true) if err != nil { return err } if expectedString != actualString { return fmt.Errorf("XML mismatch: %v != %v", expectedString, actualString) } return nil } // AssertXMLEqual compares two XML documents and identifies if the documents // contain the same values. Emits a testing error, and returns false if the // documents are not equal. func AssertXMLEqual(t T, expect, actual []byte) bool { t.Helper() if err := XMLEqual(expect, actual); err != nil { t.Errorf("expect XML documents to be equal, %v", err) return false } return true } // URLFormEqual compares two URLForm documents and identifies if the documents // contain the same values. Returns an error if the two documents are not // equal. func URLFormEqual(expectBytes, actualBytes []byte) error { expect := parseFormBody(expectBytes) actual := parseFormBody(actualBytes) if !reflect.DeepEqual(expect, actual) { return fmt.Errorf("Query mismatch: %v != %v", expect, actual) } return nil } func parseFormBody(bytes []byte) []QueryItem { str := string(bytes) // Strip out any whitespace. Significant whitespace will be encoded, and so // won't be stripped. str = strings.Join(strings.Fields(str), "") parsed := ParseRawQuery(str) sort.SliceStable(parsed, func(i, j int) bool { return parsed[i].Key < parsed[j].Key }) return parsed } // AssertURLFormEqual compares two URLForm documents and identifies if the // documents contain the same values. Emits a testing error, and returns false // if the documents are not equal. func AssertURLFormEqual(t T, expect, actual []byte) bool { t.Helper() if err := URLFormEqual(expect, actual); err != nil { t.Errorf("expect URLForm documents to be equal, %v", err) return false } return true } smithy-go-1.20.3/testing/document_test.go000066400000000000000000000247111463735525100204160ustar00rootroot00000000000000package testing import ( "strings" "testing" ) func TestAssertJSON(t *testing.T) { cases := map[string]struct { X, Y []byte Equal bool }{ "equal": { X: []byte(`{"RecursiveStruct":{"RecursiveMap":{"foo":{"NoRecurse":"foo"},"bar":{"NoRecurse":"bar"}}}}`), Y: []byte(`{"RecursiveStruct":{"RecursiveMap":{"bar":{"NoRecurse":"bar"},"foo":{"NoRecurse":"foo"}}}}`), Equal: true, }, "not equal": { X: []byte(`{"RecursiveStruct":{"RecursiveMap":{"foo":{"NoRecurse":"foo"},"bar":{"NoRecurse":"bar"}}}}`), Y: []byte(`{"RecursiveStruct":{"RecursiveMap":{"foo":{"NoRecurse":"foo"}}}}`), Equal: false, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { err := JSONEqual(c.X, c.Y) if c.Equal { if err != nil { t.Fatalf("expect JSON to be equal, %v", err) } } else if err == nil { t.Fatalf("expect JSON to be equal, %v", err) } }) } } func TestAssertURLFormEqual(t *testing.T) { cases := map[string]struct { X, Y []byte Equal bool }{ "equal": { X: []byte(`Action=QueryMaps&Version=2020-01-08&MapArg.entry.1.key=foo&MapArg.entry.1.value=Foo&MapArg.entry.2.key=bar&MapArg.entry.2.value=Bar`), Y: []byte(`Action=QueryMaps&Version=2020-01-08&MapArg.entry.2.key=bar&MapArg.entry.2.value=Bar&MapArg.entry.1.key=foo&MapArg.entry.1.value=Foo`), Equal: true, }, "strips insignificant whitespace": { X: []byte(`Foo=Bar &Baz=Bar &Bin=Foo`), Y: []byte(`Foo=Bar&Baz=Bar&Bin=Foo`), Equal: true, }, "preserves significant whitespace": { X: []byte(`Foo=Bar+&Baz=Bar%20&Bin=Foo%0A`), Y: []byte(`Foo=Bar%20&Baz=Bar+&Bin=Foo%0A`), Equal: true, }, "missing significant whitespace not equal": { X: []byte(`Foo=Bar+&Baz=Bar%20&Bin=Foo%0A%09%09%09%09&Spam=Eggs`), Y: []byte(`Foo=Bar &Baz=Bar &Bin=Foo &Spam=Eggs`), Equal: false, }, "not equal": { X: []byte(`Action=QueryMaps&Version=2020-01-08&MapArg.entry.1.key=foo&MapArg.entry.1.value=Foo&MapArg.entry.2.key=bar&MapArg.entry.2.value=Bar`), Y: []byte(`Action=QueryMaps&Version=2020-01-08&MapArg.entry.1.key=foo&MapArg.entry.1.value=Foo`), Equal: false, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { err := URLFormEqual(c.X, c.Y) if c.Equal { if err != nil { t.Fatalf("expect form to be equal, %v", err) } } else if err == nil { t.Fatalf("expect form to be equal, %v", err) } }) } } func TestEqualXMLUtil(t *testing.T) { cases := map[string]struct { expectedXML string actualXML string expectErr string }{ "empty": { expectedXML: ``, actualXML: ``, }, "emptyWithDiff": { expectedXML: ``, actualXML: ``, expectErr: "XML mismatch", }, "simpleXML": { expectedXML: ``, actualXML: ``, }, "simpleXMLWithDiff": { expectedXML: ``, actualXML: `abc`, expectErr: "XML mismatch", }, "nestedXML": { expectedXML: `123xyz`, actualXML: `123xyz`, }, "nestedXMLWithExpectedDiff": { expectedXML: `123xyz234`, actualXML: `123xyz`, expectErr: "XML mismatch", }, "nestedXMLWithActualDiff": { expectedXML: `123xyz`, actualXML: `123xyz234`, expectErr: "XML mismatch", }, "Array": { expectedXML: `xyzabc`, actualXML: `xyzabc`, }, "ArrayWithSecondDiff": { expectedXML: `xyz123`, actualXML: `xyz345`, expectErr: "XML mismatch", }, "ArrayWithFirstDiff": { expectedXML: `abc345`, actualXML: `xyz345`, expectErr: "XML mismatch", }, "ArrayWithMixedDiff": { expectedXML: `345xyz`, actualXML: `xyz345`, }, "ArrayWithRepetitiveMembers": { expectedXML: `xyzxyz`, actualXML: `xyzxyz`, }, "Map": { expectedXML: `abc123cde356`, actualXML: `abc123cde356`, }, "MapWithFirstDiff": { expectedXML: `bcf123cde356`, actualXML: `abc123cde356`, expectErr: "XML mismatch", }, "MapWithSecondDiff": { expectedXML: `abc123cdeabc`, actualXML: `abc123cde356`, expectErr: "XML mismatch", }, "MapWithMixedDiff": { expectedXML: `cde356abc123`, actualXML: `abc123cde356`, }, "MismatchCheckforKeyValue": { expectedXML: `cdeabcabc356`, actualXML: `abc123cde356`, expectErr: "XML mismatch", }, "MixMapAndListNestedXML": { expectedXML: `mem1mem2abcabcxyzgamma`, actualXML: `mem1mem2abcabcxyzgamma`, }, "MixMapAndListNestedXMLWithDiff": { expectedXML: `mem1mem2abcabcxyzgamma`, actualXML: `mem1mem2abcabcxyzgamma`, expectErr: "XML mismatch", }, "xmlWithNamespaceAndAttr": { expectedXML: `value`, actualXML: `value`, }, "xmlUnorderedAttributes": { expectedXML: `v`, actualXML: `v`, }, "xmlAttributesWithDiff": { expectedXML: `v`, actualXML: `v`, expectErr: "XML mismatch", }, "xmlUnorderedNamespaces": { expectedXML: `v`, actualXML: `v`, }, "xmlNamespaceWithDiff": { expectedXML: `v`, actualXML: `v`, expectErr: "XML mismatch", }, "NestedWithNamespaceAndAttributes": { expectedXML: `mem1mem2abcabcxyzgamma`, actualXML: `mem1mem2abcabcxyzgamma`, }, "NestedWithNamespaceAndAttributesWithDiff": { expectedXML: `mem2mem2abcabcxyzgamma`, actualXML: `mem1mem2abcabcxyzgamma`, expectErr: "XML mismatch", }, "MalformedXML": { expectedXML: `aa2vbb2w`, actualXML: `aa2vbb2w`, expectErr: "malformed xml", }, } for name, c := range cases { t.Run(name, func(t *testing.T) { actual := []byte(c.actualXML) expected := []byte(c.expectedXML) err := XMLEqual(actual, expected) if err != nil { if len(c.expectErr) == 0 { t.Fatalf("expected no error while parsing xml, got %v", err) } else if !strings.Contains(err.Error(), c.expectErr) { t.Fatalf("expected expected XML err to contain %s, got %s", c.expectErr, err.Error()) } } else if len(c.expectErr) != 0 { t.Fatalf("expected error %s, got none", c.expectErr) } }) } } smithy-go-1.20.3/testing/errors.go000066400000000000000000000003401463735525100170450ustar00rootroot00000000000000package testing import "strings" // errors provides batching errors type errors []error func (es errors) Error() string { var b strings.Builder for _, e := range es { b.WriteString(e.Error()) } return b.String() } smithy-go-1.20.3/testing/gzip.go000066400000000000000000000010511463735525100165020ustar00rootroot00000000000000package testing import ( "bytes" "compress/gzip" "fmt" "io" ) func GzipCompareCompressBytes(expect []byte, actual io.Reader) error { content, err := gzip.NewReader(actual) if err != nil { return fmt.Errorf("error while reading request") } var actualBytes bytes.Buffer _, err = actualBytes.ReadFrom(content) if err != nil { return fmt.Errorf("error while unzipping request payload") } if e, a := expect, actualBytes.Bytes(); !bytes.Equal(e, a) { return fmt.Errorf("expect unzipped content to be %s, got %s", e, a) } return nil } smithy-go-1.20.3/testing/reader.go000066400000000000000000000014161463735525100170000ustar00rootroot00000000000000package testing import ( "io" "sync" ) // ByteLoop provides an io.ReadCloser that always fills the read byte slice // with repeating value, until closed. type ByteLoop struct { value byte closed bool mu sync.RWMutex } // Read populates the passed in byte slice with the value the ByteLoop was // created with. Returns the size of bytes written. If the reader is closed, // io.EOF will be returned. func (l *ByteLoop) Read(p []byte) (size int, err error) { l.mu.RLock() defer l.mu.RUnlock() if l.closed { return 0, io.EOF } for i := 0; i < len(p); i++ { p[i] = l.value } return len(p), nil } // Close closes the ByteLoop, and prevents any further reading. func (l *ByteLoop) Close() error { l.mu.Lock() defer l.mu.Unlock() l.closed = true return nil } smithy-go-1.20.3/testing/rest.go000066400000000000000000000141471463735525100165200ustar00rootroot00000000000000package testing import ( "fmt" "net/http" "strings" ) // HasHeader compares the header values and identifies if the actual header // set includes all values specified in the expect set. Returns an error if not. func HasHeader(expect, actual http.Header) error { var errs errors for key, es := range expect { as := actual.Values(key) if len(as) == 0 { errs = append(errs, fmt.Errorf("expect %v header in %v", key, actual)) continue } // Join the list of header values together for consistent // comparison between common separated sets, and individual header // key/value pairs repeated. e := strings.Join(es, ", ") a := strings.Join(as, ", ") if e != a { errs = append(errs, fmt.Errorf("expect %v=%v to match %v", key, e, a)) continue } } return errs } // AssertHasHeader compares the header values and identifies if the actual // header set includes all values specified in the expect set. Emits a testing // error, and returns false if the headers are not equal. func AssertHasHeader(t T, expect, actual http.Header) bool { t.Helper() if err := HasHeader(expect, actual); err != nil { for _, e := range err.(errors) { t.Error(e) } return false } return true } // HasHeaderKeys validates that header set contains all keys expected. Returns // an error if a header key is not in the header set. func HasHeaderKeys(keys []string, actual http.Header) error { var errs errors for _, key := range keys { if vs := actual.Values(key); len(vs) == 0 { errs = append(errs, fmt.Errorf("expect %v key in %v", key, actual)) continue } } return errs } // AssertHasHeaderKeys validates that header set contains all keys expected. // Emits a testing error, and returns false if the headers are not equal. func AssertHasHeaderKeys(t T, keys []string, actual http.Header) bool { t.Helper() if err := HasHeaderKeys(keys, actual); err != nil { for _, e := range err.(errors) { t.Error(e) } return false } return true } // NotHaveHeaderKeys validates that header set does not contains any of the // keys. Returns an error if a header key is found in the header set. func NotHaveHeaderKeys(keys []string, actual http.Header) error { var errs errors for _, k := range keys { if vs := actual.Values(k); len(vs) != 0 { errs = append(errs, fmt.Errorf("expect %v key not in %v", k, actual)) continue } } return errs } // AssertNotHaveHeaderKeys validates that header set does not contains any of // the keys. Emits a testing error, and returns false if the header contains // the any of the keys equal. func AssertNotHaveHeaderKeys(t T, keys []string, actual http.Header) bool { t.Helper() if err := NotHaveHeaderKeys(keys, actual); err != nil { for _, e := range err.(errors) { t.Error(e) } return false } return true } // QueryItem provides an escaped key and value struct for values from a raw // query string. type QueryItem struct { Key string Value string } // ParseRawQuery returns a slice of QueryItems extracted from the raw query // string. The parsed QueryItems preserve escaping of key and values. // // All + escape characters are replaced with %20 for consistent escaping // pattern. func ParseRawQuery(rawQuery string) (items []QueryItem) { for _, item := range strings.Split(rawQuery, `&`) { parts := strings.SplitN(item, `=`, 2) var value string if len(parts) > 1 { value = parts[1] } items = append(items, QueryItem{ // Go Query encoder escapes space as `+` whereas smithy protocol // tests expect `%20`. Key: strings.ReplaceAll(parts[0], `+`, `%20`), Value: strings.ReplaceAll(value, `+`, `%20`), }) } return items } // HasQuery validates that the expected set of query items are present in // the actual set. Returns an error if any of the expected set are not found in // the actual. func HasQuery(expect, actual []QueryItem) error { var errs errors for _, item := range expect { var found bool for _, v := range actual { if item.Key == v.Key && item.Value == v.Value { found = true break } } if !found { errs = append(errs, fmt.Errorf("expect %v query item in %v", item, actual)) } } return errs } // AssertHasQuery validates that the expected set of query items are // present in the actual set. Emits a testing error, and returns false if any // of the expected set are not found in the actual. func AssertHasQuery(t T, expect, actual []QueryItem) bool { t.Helper() if err := HasQuery(expect, actual); err != nil { for _, e := range err.(errors) { t.Error(e.Error()) } return false } return true } // HasQueryKeys validates that the actual set of query items contains the keys // provided. Returns an error if any key is not found. func HasQueryKeys(keys []string, actual []QueryItem) error { var errs errors for _, key := range keys { var found bool for _, v := range actual { if key == v.Key { found = true break } } if !found { errs = append(errs, fmt.Errorf("expect %v query key in %v", key, actual)) } } return errs } // AssertHasQueryKeys validates that the actual set of query items contains the // keys provided. Emits a testing error if any key is not found. func AssertHasQueryKeys(t T, keys []string, actual []QueryItem) bool { t.Helper() if err := HasQueryKeys(keys, actual); err != nil { for _, e := range err.(errors) { t.Error(e) } return false } return true } // NotHaveQueryKeys validates that the actual set of query items does not // contain the keys provided. Returns an error if any key is found. func NotHaveQueryKeys(keys []string, actual []QueryItem) error { var errs errors for _, key := range keys { for _, v := range actual { if key == v.Key { errs = append(errs, fmt.Errorf("expect %v query key not in %v", key, actual)) continue } } } return errs } // AssertNotHaveQueryKeys validates that the actual set of query items does not // contains the keys provided. Emits a testing error if any key is found. func AssertNotHaveQueryKeys(t T, keys []string, actual []QueryItem) bool { t.Helper() if err := NotHaveQueryKeys(keys, actual); err != nil { for _, e := range err.(errors) { t.Error(e) } return false } return true } smithy-go-1.20.3/testing/struct.go000066400000000000000000000135161463735525100170660ustar00rootroot00000000000000package testing import ( "bytes" "encoding/hex" "fmt" "io" "math" "reflect" "github.com/aws/smithy-go/document" "github.com/aws/smithy-go/middleware" ) // CompareValues compares two values to determine if they are equal, // specialized for comparison of SDK operation output types. // // CompareValues expects the two values to be of the same underlying type. // Doing otherwise will result in undefined behavior. // // The third variadic argument is vestigial from a previous implementation that // depended on go-cmp. Values passed therein have no effect. func CompareValues(expect, actual interface{}, _ ...interface{}) error { return deepEqual(reflect.ValueOf(expect), reflect.ValueOf(actual), "") } func deepEqual(expect, actual reflect.Value, path string) error { if et, at := expect.Kind(), actual.Kind(); et != at { return fmt.Errorf("%s: kind %s != %s", path, et, at) } // there are a handful of short-circuit cases here within the context of // operation responses: // - ResultMetadata (we don't care) // - document.Interface (check for marshaled []byte equality) // - io.Reader (check for Read() []byte equality) ei, ai := expect.Interface(), actual.Interface() if _, _, ok := asMetadatas(ei, ai); ok { return nil } if e, a, ok := asDocuments(ei, ai); ok { if !compareDocumentTypes(e, a) { return fmt.Errorf("%s: document values unequal", path) } return nil } if e, a, ok := asReaders(ei, ai); ok { if err := CompareReaders(e, a); err != nil { return fmt.Errorf("%s: %w", path, err) } return nil } switch expect.Kind() { case reflect.Pointer: if expect.Type() != actual.Type() { return fmt.Errorf("%s: type mismatch", path) } expect = deref(expect) actual = deref(actual) ek, ak := expect.Kind(), actual.Kind() if ek == reflect.Invalid || ak == reflect.Invalid { // one was a nil pointer, so they both must be nil if ek == ak { return nil } return fmt.Errorf("%s: %s != %s", path, fmtNil(ek), fmtNil(ak)) } if err := deepEqual(expect, actual, path); err != nil { return err } return nil case reflect.Slice: if expect.Len() != actual.Len() { return fmt.Errorf("%s: slice length unequal", path) } for i := 0; i < expect.Len(); i++ { ipath := fmt.Sprintf("%s[%d]", path, i) if err := deepEqual(expect.Index(i), actual.Index(i), ipath); err != nil { return err } } return nil case reflect.Map: if expect.Len() != actual.Len() { return fmt.Errorf("%s: map length unequal", path) } for _, k := range expect.MapKeys() { kpath := fmt.Sprintf("%s[%q]", path, k.String()) if err := deepEqual(expect.MapIndex(k), actual.MapIndex(k), kpath); err != nil { return err } } return nil case reflect.Struct: for i := 0; i < expect.NumField(); i++ { if !expect.Field(i).CanInterface() { continue // unexported } fpath := fmt.Sprintf("%s.%s", path, expect.Type().Field(i).Name) if err := deepEqual(expect.Field(i), actual.Field(i), fpath); err != nil { return err } } return nil case reflect.Float32, reflect.Float64: ef, af := expect.Float(), actual.Float() ebits, abits := math.Float64bits(ef), math.Float64bits(af) if enan, anan := math.IsNaN(ef), math.IsNaN(af); enan || anan { if enan != anan { return fmt.Errorf("%s: NaN: float64(0x%x) != float64(0x%x)", path, ebits, abits) } return nil } if ebits != abits { return fmt.Errorf("%s: float64(0x%x) != float64(0x%x)", path, ebits, abits) } return nil default: // everything else is just scalars and can be delegated if !reflect.DeepEqual(ei, ai) { return fmt.Errorf("%s: %v != %v", path, ei, ai) } return nil } } func asMetadatas(i, j interface{}) (ii, jj middleware.Metadata, ok bool) { ii, iok := i.(middleware.Metadata) jj, jok := j.(middleware.Metadata) return ii, jj, iok || jok } func asDocuments(i, j interface{}) (ii, jj documentInterface, ok bool) { ii, iok := i.(documentInterface) jj, jok := j.(documentInterface) return ii, jj, iok || jok } func asReaders(i, j interface{}) (ii, jj io.Reader, ok bool) { ii, iok := i.(io.Reader) jj, jok := j.(io.Reader) return ii, jj, iok || jok } func deref(v reflect.Value) reflect.Value { switch v.Kind() { case reflect.Interface, reflect.Ptr: for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr { v = v.Elem() } } return v } type documentInterface interface { document.Marshaler document.Unmarshaler } func compareDocumentTypes(x documentInterface, y documentInterface) bool { if x == nil { x = nopMarshaler{} } if y == nil { y = nopMarshaler{} } xBytes, err := x.MarshalSmithyDocument() if err != nil { panic(fmt.Sprintf("MarshalSmithyDocument error: %v", err)) } yBytes, err := y.MarshalSmithyDocument() if err != nil { panic(fmt.Sprintf("MarshalSmithyDocument error: %v", err)) } return JSONEqual(xBytes, yBytes) == nil } // CompareReaders two io.Reader values together to determine if they are equal. // Will read the contents of the readers until they are empty. func CompareReaders(expect, actual io.Reader) error { if expect == nil { expect = nopReader{} } if actual == nil { actual = nopReader{} } e, err := io.ReadAll(expect) if err != nil { return fmt.Errorf("failed to read expect body, %w", err) } a, err := io.ReadAll(actual) if err != nil { return fmt.Errorf("failed to read actual body, %w", err) } if !bytes.Equal(e, a) { return fmt.Errorf("bytes do not match\nexpect:\n%s\nactual:\n%s", hex.Dump(e), hex.Dump(a)) } return nil } func fmtNil(k reflect.Kind) string { if k == reflect.Invalid { return "nil" } return "non-nil" } type nopReader struct{} func (nopReader) Read(p []byte) (int, error) { return 0, io.EOF } type nopMarshaler struct{} func (nopMarshaler) MarshalSmithyDocument() ([]byte, error) { return nil, nil } func (nopMarshaler) UnmarshalSmithyDocument(v interface{}) error { return nil } smithy-go-1.20.3/testing/struct_test.go000066400000000000000000000143621463735525100201250ustar00rootroot00000000000000package testing import ( "bytes" "io" "io/ioutil" "math" "strings" "testing" "github.com/aws/smithy-go/middleware" "github.com/aws/smithy-go/ptr" ) func TestCompareValues(t *testing.T) { const float64NaN = 0x7fffffff_ffffffff // mantissa flipped all the way on cases := map[string]struct { A, B interface{} ExpectErr string }{ "totally different types": { A: 1, B: struct { Foo string Bar int }{ Foo: "abc", Bar: 123, }, ExpectErr: ": kind int != struct", }, "simple match": { A: struct { Foo string Bar int Metadata middleware.Metadata }{ Foo: "abc", Bar: 123, Metadata: func() middleware.Metadata { var md middleware.Metadata md.Set(1, 1) return md }(), }, B: struct { Foo string Bar int Metadata middleware.Metadata }{ Foo: "abc", Bar: 123, Metadata: middleware.Metadata{}, // different, shouldn't matter }, }, "simple diff": { A: struct { Foo string Bar int }{ Foo: "abc", Bar: 123, }, B: struct { Foo string Bar int }{ Foo: "abc", Bar: 456, }, ExpectErr: ".Bar: 123 != 456", }, "reader match": { A: struct { Foo io.Reader Bar int }{ Foo: bytes.NewBuffer([]byte("abc123")), Bar: 123, }, B: struct { Foo io.Reader Bar int }{ Foo: ioutil.NopCloser(strings.NewReader("abc123")), Bar: 123, }, }, "reader diff": { A: struct { Foo io.Reader Bar int }{ Foo: bytes.NewBuffer([]byte("abc123")), Bar: 123, }, B: struct { Foo io.Reader Bar int }{ Foo: ioutil.NopCloser(strings.NewReader("123abc")), Bar: 123, }, ExpectErr: ".Foo: bytes do not match", }, "float match": { A: struct { Foo float64 Bar int }{ Foo: math.Float64frombits(float64NaN), Bar: 123, }, B: struct { Foo float64 Bar int }{ Foo: math.Float64frombits(float64NaN), Bar: 123, }, }, "float diff NaN": { A: struct { Foo float64 Bar int }{ Foo: math.Float64frombits(float64NaN), Bar: 123, }, B: struct { Foo float64 Bar int }{ Foo: math.Float64frombits(float64NaN - 1), Bar: 123, }, }, "float diff": { A: struct { Foo float64 Bar int }{ Foo: math.Float64frombits(0x100), Bar: 123, }, B: struct { Foo float64 Bar int }{ Foo: math.Float64frombits(0x101), Bar: 123, }, ExpectErr: ".Foo: float64(0x100) != float64(0x101)", }, "document equal": { A: &mockDocumentMarshaler{[]byte("123"), nil}, B: &mockDocumentMarshaler{[]byte("123"), nil}, }, "document unequal": { A: &mockDocumentMarshaler{[]byte("123"), nil}, B: &mockDocumentMarshaler{[]byte("124"), nil}, ExpectErr: ": document values unequal", }, "slice equal": { A: []struct { Bar int }{{0}, {1}}, B: []struct { Bar int }{{0}, {1}}, }, "slice length unequal": { A: []struct { Bar int }{{0}}, B: []struct { Bar int }{{0}, {1}}, ExpectErr: "slice length unequal", }, "slice value unequal": { A: []struct { Bar int }{{2}, {1}, {0}}, B: []struct { Bar int }{{2}, {0}, {1}}, ExpectErr: "[1].Bar: 1 != 0", }, "map equal": { A: map[string]struct { Bar int }{ "foo": {0}, "bar": {1}, }, B: map[string]struct { Bar int }{ "bar": {1}, "foo": {0}, }, }, "map length unequal": { A: map[string]struct { Bar int }{ "foo": {0}, "bar": {1}, }, B: map[string]struct { Bar int }{ "foo": {0}, }, ExpectErr: "map length unequal", }, "map value unequal": { A: map[string]struct { IntField int }{ "foo": {0}, "bar": {1}, }, B: map[string]struct { IntField int }{ "bar": {1}, "foo": {1}, }, ExpectErr: `["foo"].IntField: 0 != 1`, }, "handles deref, nil equal": { A: struct { Int *int }{nil}, B: struct { Int *int }{nil}, }, "handles deref, value equal": { A: struct { Int *int }{ptr.Int(12)}, B: struct { Int *int }{ptr.Int(12)}, }, "handles deref, different types are unequal": { A: struct { Int *int }{nil}, B: struct { Int *string }{nil}, ExpectErr: ".Int: type mismatch", }, "handles deref, unequal": { A: struct { Int *int }{ptr.Int(12)}, B: struct { Int *int }{nil}, ExpectErr: ".Int: non-nil != nil", }, "handles deref, unequal switched": { A: struct { Int *int }{nil}, B: struct { Int *int }{ptr.Int(12)}, ExpectErr: ".Int: nil != non-nil", }, } for name, c := range cases { t.Run(name, func(t *testing.T) { err := CompareValues(c.A, c.B) if len(c.ExpectErr) != 0 { if err == nil { t.Fatalf("expect error, got none") } if e, a := c.ExpectErr, err.Error(); !strings.Contains(a, e) { t.Fatalf("expect error to contain %v, got %v", e, a) } return } if err != nil { t.Fatalf("expect no error, got %v", err) } }) } } func TestCompareValues_Document(t *testing.T) { cases := map[string]struct { A, B interface{} ExpectErr string }{ "equal": { A: &mockDocumentMarshaler{[]byte("123"), nil}, B: &mockDocumentMarshaler{[]byte("123"), nil}, }, "unequal": { A: &mockDocumentMarshaler{[]byte("123"), nil}, B: &mockDocumentMarshaler{[]byte("124"), nil}, ExpectErr: ": document values unequal", }, } for name, c := range cases { t.Run(name, func(t *testing.T) { err := CompareValues(c.A, c.B) if len(c.ExpectErr) != 0 { if err == nil { t.Errorf("expect error, got none") } if e, a := c.ExpectErr, err.Error(); !strings.Contains(a, e) { t.Errorf("expect error to contain %v, got %v", e, a) } return } if err != nil { t.Errorf("expect no error, got %v", err) } }) } } type mockDocumentMarshaler struct { p []byte err error } var _ documentInterface = (*mockDocumentMarshaler)(nil) func (m *mockDocumentMarshaler) MarshalSmithyDocument() ([]byte, error) { return m.p, m.err } func (m *mockDocumentMarshaler) UnmarshalSmithyDocument(v interface{}) error { return nil } smithy-go-1.20.3/testing/xml/000077500000000000000000000000001463735525100160055ustar00rootroot00000000000000smithy-go-1.20.3/testing/xml/doc.go000066400000000000000000000007271463735525100171070ustar00rootroot00000000000000// package xml is xml testing package that supports xml comparison utility. // The package consists of XMLToStruct and StructToXML utils that help sort xml elements // as per their nesting level. XMLToStruct function converts an xml document into a sorted // tree node structure, while StructToXML converts the sorted xml nodes into a sorted xml document. // SortXML function should be used to sort an xml document. It can be configured to ignore indentation package xml smithy-go-1.20.3/testing/xml/sort.go000066400000000000000000000017761463735525100173360ustar00rootroot00000000000000package xml import ( "bytes" "encoding/xml" "io" "strings" ) type xmlAttrSlice []xml.Attr func (x xmlAttrSlice) Len() int { return len(x) } func (x xmlAttrSlice) Less(i, j int) bool { spaceI, spaceJ := x[i].Name.Space, x[j].Name.Space localI, localJ := x[i].Name.Local, x[j].Name.Local valueI, valueJ := x[i].Value, x[j].Value spaceCmp := strings.Compare(spaceI, spaceJ) localCmp := strings.Compare(localI, localJ) valueCmp := strings.Compare(valueI, valueJ) if spaceCmp == -1 || (spaceCmp == 0 && (localCmp == -1 || (localCmp == 0 && valueCmp == -1))) { return true } return false } func (x xmlAttrSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] } // SortXML sorts the reader's XML elements func SortXML(r io.Reader, ignoreIndentation bool) (string, error) { var buf bytes.Buffer d := xml.NewDecoder(r) root, err := XMLToStruct(d, nil, ignoreIndentation) if err != nil { return buf.String(), err } e := xml.NewEncoder(&buf) err = StructToXML(e, root, true) return buf.String(), err } smithy-go-1.20.3/testing/xml/sort_test.go000066400000000000000000000007551463735525100203710ustar00rootroot00000000000000package xml import ( "bytes" "testing" ) func TestSortXML(t *testing.T) { xmlInput := bytes.NewReader([]byte(`xyz1231`)) sortedXML, err := SortXML(xmlInput, false) expectedsortedXML := `123xyz1` if err != nil { t.Fatalf("expected no error, got %v", err) } if expectedsortedXML != sortedXML { t.Fatalf("found diff: %v != %v", expectedsortedXML, sortedXML) } } smithy-go-1.20.3/testing/xml/xmlToStruct.go000066400000000000000000000233731463735525100206540ustar00rootroot00000000000000package xml import ( "encoding/xml" "fmt" "io" "sort" "strings" ) // A XMLNode contains the values to be encoded or decoded. type XMLNode struct { Name xml.Name `json:",omitempty"` Children map[string][]*XMLNode `json:",omitempty"` Text string `json:",omitempty"` Attr []xml.Attr `json:",omitempty"` namespaces map[string]string parent *XMLNode } // NewXMLElement returns a pointer to a new XMLNode initialized to default values. func NewXMLElement(name xml.Name) *XMLNode { return &XMLNode{ Name: name, Children: map[string][]*XMLNode{}, Attr: []xml.Attr{}, } } // AddChild adds child to the XMLNode. func (n *XMLNode) AddChild(child *XMLNode) { child.parent = n if _, ok := n.Children[child.Name.Local]; !ok { // flattened will have multiple children with same tag name n.Children[child.Name.Local] = []*XMLNode{} } n.Children[child.Name.Local] = append(n.Children[child.Name.Local], child) } // XMLToStruct converts a xml.Decoder stream to XMLNode with nested values. func XMLToStruct(d *xml.Decoder, s *xml.StartElement, ignoreIndentation bool) (*XMLNode, error) { out := &XMLNode{} for { tok, err := d.Token() if err != nil { if err == io.EOF { break } else { return out, err } } if tok == nil { break } switch typed := tok.(type) { case xml.CharData: text := string(typed.Copy()) if ignoreIndentation { text = strings.TrimSpace(text) } if len(text) != 0 { out.Text = text } case xml.StartElement: el := typed.Copy() out.Attr = el.Attr if out.Children == nil { out.Children = map[string][]*XMLNode{} } name := typed.Name.Local slice := out.Children[name] if slice == nil { slice = []*XMLNode{} } node, e := XMLToStruct(d, &el, ignoreIndentation) out.findNamespaces() if e != nil { return out, e } node.Name = typed.Name node.findNamespaces() // Add attributes onto the node node.Attr = el.Attr tempOut := *out // Save into a temp variable, simply because out gets squashed during // loop iterations node.parent = &tempOut slice = append(slice, node) out.Children[name] = slice case xml.EndElement: if s != nil && s.Name.Local == typed.Name.Local { // matching end token return out, nil } out = &XMLNode{} } } return out, nil } func (n *XMLNode) findNamespaces() { ns := map[string]string{} for _, a := range n.Attr { if a.Name.Space == "xmlns" { ns[a.Value] = a.Name.Local } } n.namespaces = ns } func (n *XMLNode) findElem(name string) (string, bool) { for node := n; node != nil; node = node.parent { for _, a := range node.Attr { namespace := a.Name.Space if v, ok := node.namespaces[namespace]; ok { namespace = v } if name == fmt.Sprintf("%s:%s", namespace, a.Name.Local) { return a.Value, true } } } return "", false } // StructToXML writes an XMLNode to a xml.Encoder as tokens. func StructToXML(e *xml.Encoder, node *XMLNode, sorted bool) error { var err error // Sort Attributes attrs := node.Attr if sorted { sortedAttrs := make([]xml.Attr, len(attrs)) for _, k := range node.Attr { sortedAttrs = append(sortedAttrs, k) } sort.Sort(xmlAttrSlice(sortedAttrs)) attrs = sortedAttrs } st := xml.StartElement{Name: node.Name, Attr: attrs} e.EncodeToken(st) // return fmt.Errorf("encoder string : %s, %s, %s", node.Name.Local, node.Name.Space, st.Attr) if node.Text != "" { e.EncodeToken(xml.CharData([]byte(node.Text))) } else if sorted { sortedNames := []string{} for k := range node.Children { sortedNames = append(sortedNames, k) } sort.Strings(sortedNames) for _, k := range sortedNames { // we should sort the []*xml.Node for each key if len >1 flattenedNodes := node.Children[k] // Meaning this has multiple nodes if len(flattenedNodes) > 1 { // sort flattened nodes flattenedNodes, err = sortFlattenedNodes(flattenedNodes) if err != nil { return err } } for _, v := range flattenedNodes { err = StructToXML(e, v, sorted) if err != nil { return err } } } } else { for _, c := range node.Children { for _, v := range c { err = StructToXML(e, v, sorted) if err != nil { return err } } } } e.EncodeToken(xml.EndElement{Name: node.Name}) return e.Flush() } // sortFlattenedNodes sorts nodes with nodes having same element tag // but overall different values. The function will return list of pointer to // XMLNode and an error. // // Overall sort order is followed is: // Nodes with concrete value (no nested node as value) are given precedence // and are added to list after sorting them // // Next nested nodes within a flattened list are given precedence. // // Next nodes within a flattened map are sorted based on either key or value // which ever has lower value and then added to the global sorted list. // If value was initially chosen, but has nested nodes; key will be chosen as comparable // as it is unique and will always have concrete data ie. string. func sortFlattenedNodes(nodes []*XMLNode) ([]*XMLNode, error) { var sortedNodes []*XMLNode // concreteNodeMap stores concrete value associated with a list of nodes // This is possible in case multiple members of a flatList has same values. concreteNodeMap := make(map[string][]*XMLNode, 0) // flatListNodeMap stores flat list or wrapped list members associated with a list of nodes // This will have only flattened list with members that are Nodes and not concrete values. flatListNodeMap := make(map[string][]*XMLNode, 0) // flatMapNodeMap stores flat map or map entry members associated with a list of nodes // This will have only flattened map concrete value members. It is possible to limit this // to concrete value as map key is expected to be concrete. flatMapNodeMap := make(map[string][]*XMLNode, 0) // nodes with concrete value are prioritized and appended based on sorting order sortedNodesWithConcreteValue := []string{} // list with nested nodes are second in priority and appended based on sorting order sortedNodesWithListValue := []string{} // map are last in priority and appended based on sorting order sortedNodesWithMapValue := []string{} for _, node := range nodes { // node has no children element, then we consider it as having concrete value if len(node.Children) == 0 { sortedNodesWithConcreteValue = append(sortedNodesWithConcreteValue, node.Text) if v, ok := concreteNodeMap[node.Text]; ok { concreteNodeMap[node.Text] = append(v, node) } else { concreteNodeMap[node.Text] = []*XMLNode{node} } } // if node has a single child, then it is a flattened list node if len(node.Children) == 1 { for _, nestedNodes := range node.Children { nestedNodeName := nestedNodes[0].Name.Local // append to sorted node name for list value sortedNodesWithListValue = append(sortedNodesWithListValue, nestedNodeName) if v, ok := flatListNodeMap[nestedNodeName]; ok { flatListNodeMap[nestedNodeName] = append(v, nestedNodes[0]) } else { flatListNodeMap[nestedNodeName] = []*XMLNode{nestedNodes[0]} } } } // if node has two children, then it is a flattened map node if len(node.Children) == 2 { nestedPair := []*XMLNode{} for _, k := range node.Children { nestedPair = append(nestedPair, k[0]) } comparableValues := []string{nestedPair[0].Name.Local, nestedPair[1].Name.Local} sort.Strings(comparableValues) comparableValue := comparableValues[0] for _, nestedNode := range nestedPair { if comparableValue == nestedNode.Name.Local && len(nestedNode.Children) != 0 { // if value was selected and is nested node, skip it and use key instead comparableValue = comparableValues[1] continue } // now we are certain there is no nested node if comparableValue == nestedNode.Name.Local { // get chardata for comparison comparableValue = nestedNode.Text sortedNodesWithMapValue = append(sortedNodesWithMapValue, comparableValue) if v, ok := flatMapNodeMap[comparableValue]; ok { flatMapNodeMap[comparableValue] = append(v, node) } else { flatMapNodeMap[comparableValue] = []*XMLNode{node} } break } } } // we don't support multiple same name nodes in an xml doc except for in flattened maps, list. if len(node.Children) > 2 { return nodes, fmt.Errorf("malformed xml: multiple nodes with same key name exist, " + "but are not associated with flattened maps (2 children) or list (0 or 1 child)") } } // sort concrete value node name list and append corresponding nodes // to sortedNodes sort.Strings(sortedNodesWithConcreteValue) for _, name := range sortedNodesWithConcreteValue { for _, node := range concreteNodeMap[name] { sortedNodes = append(sortedNodes, node) } } // sort nested nodes with a list and append corresponding nodes // to sortedNodes sort.Strings(sortedNodesWithListValue) for _, name := range sortedNodesWithListValue { // if two nested nodes have same name, then sort them separately. if len(flatListNodeMap[name]) > 1 { // return nodes, fmt.Errorf("flat list node name are %s %v", flatListNodeMap[name][0].Name.Local, len(flatListNodeMap[name])) nestedFlattenedList, err := sortFlattenedNodes(flatListNodeMap[name]) if err != nil { return nodes, err } // append the identical but sorted nodes for _, nestedNode := range nestedFlattenedList { sortedNodes = append(sortedNodes, nestedNode) } } else { // append the sorted nodes sortedNodes = append(sortedNodes, flatListNodeMap[name][0]) } } // sorted nodes with a map and append corresponding nodes to sortedNodes sort.Strings(sortedNodesWithMapValue) for _, name := range sortedNodesWithMapValue { sortedNodes = append(sortedNodes, flatMapNodeMap[name][0]) } return sortedNodes, nil } smithy-go-1.20.3/time/000077500000000000000000000000001463735525100144665ustar00rootroot00000000000000smithy-go-1.20.3/time/time.go000066400000000000000000000065201463735525100157560ustar00rootroot00000000000000package time import ( "context" "fmt" "math/big" "strings" "time" ) const ( // dateTimeFormat is a IMF-fixdate formatted RFC3339 section 5.6 dateTimeFormatInput = "2006-01-02T15:04:05.999999999Z" dateTimeFormatInputNoZ = "2006-01-02T15:04:05.999999999" dateTimeFormatOutput = "2006-01-02T15:04:05.999Z" // httpDateFormat is a date time defined by RFC 7231#section-7.1.1.1 // IMF-fixdate with no UTC offset. httpDateFormat = "Mon, 02 Jan 2006 15:04:05 GMT" // Additional formats needed for compatibility. httpDateFormatSingleDigitDay = "Mon, _2 Jan 2006 15:04:05 GMT" httpDateFormatSingleDigitDayTwoDigitYear = "Mon, _2 Jan 06 15:04:05 GMT" ) var millisecondFloat = big.NewFloat(1e3) // FormatDateTime formats value as a date-time, (RFC3339 section 5.6) // // Example: 1985-04-12T23:20:50.52Z func FormatDateTime(value time.Time) string { return value.UTC().Format(dateTimeFormatOutput) } // ParseDateTime parses a string as a date-time, (RFC3339 section 5.6) // // Example: 1985-04-12T23:20:50.52Z func ParseDateTime(value string) (time.Time, error) { return tryParse(value, dateTimeFormatInput, dateTimeFormatInputNoZ, time.RFC3339Nano, time.RFC3339, ) } // FormatHTTPDate formats value as a http-date, (RFC 7231#section-7.1.1.1 IMF-fixdate) // // Example: Tue, 29 Apr 2014 18:30:38 GMT func FormatHTTPDate(value time.Time) string { return value.UTC().Format(httpDateFormat) } // ParseHTTPDate parses a string as a http-date, (RFC 7231#section-7.1.1.1 IMF-fixdate) // // Example: Tue, 29 Apr 2014 18:30:38 GMT func ParseHTTPDate(value string) (time.Time, error) { return tryParse(value, httpDateFormat, httpDateFormatSingleDigitDay, httpDateFormatSingleDigitDayTwoDigitYear, time.RFC850, time.ANSIC, ) } // FormatEpochSeconds returns value as a Unix time in seconds with with decimal precision // // Example: 1515531081.123 func FormatEpochSeconds(value time.Time) float64 { ms := value.UnixNano() / int64(time.Millisecond) return float64(ms) / 1e3 } // ParseEpochSeconds returns value as a Unix time in seconds with with decimal precision // // Example: 1515531081.123 func ParseEpochSeconds(value float64) time.Time { f := big.NewFloat(value) f = f.Mul(f, millisecondFloat) i, _ := f.Int64() // Offset to `UTC` because time.Unix returns the time value based on system // local setting. return time.Unix(0, i*1e6).UTC() } func tryParse(v string, formats ...string) (time.Time, error) { var errs parseErrors for _, f := range formats { t, err := time.Parse(f, v) if err != nil { errs = append(errs, parseError{ Format: f, Err: err, }) continue } return t, nil } return time.Time{}, fmt.Errorf("unable to parse time string, %w", errs) } type parseErrors []parseError func (es parseErrors) Error() string { var s strings.Builder for _, e := range es { fmt.Fprintf(&s, "\n * %q: %v", e.Format, e.Err) } return "parse errors:" + s.String() } type parseError struct { Format string Err error } // SleepWithContext will wait for the timer duration to expire, or until the context // is canceled. Whichever happens first. If the context is canceled the // Context's error will be returned. func SleepWithContext(ctx context.Context, dur time.Duration) error { t := time.NewTimer(dur) defer t.Stop() select { case <-t.C: break case <-ctx.Done(): return ctx.Err() } return nil } smithy-go-1.20.3/time/time_test.go000066400000000000000000000146421463735525100170210ustar00rootroot00000000000000package time import ( "math" "strconv" "testing" "time" ) func TestDateTime(t *testing.T) { cases := map[string]struct { TimeString string TimeValue time.Time SymmetricString bool }{ "no offset": { TimeString: "1985-04-12T23:20:50.52Z", TimeValue: time.Date(1985, 4, 12, 23, 20, 50, int(520*time.Millisecond), time.UTC), SymmetricString: true, }, "no offset, no Z": { TimeString: "1985-04-12T23:20:50.524", TimeValue: time.Date(1985, 4, 12, 23, 20, 50, int(524*time.Millisecond), time.UTC), SymmetricString: false, }, "with negative offset": { TimeString: "1985-04-12T23:20:50.52-07:00", TimeValue: time.Date(1985, 4, 12, 23, 20, 50, int(520*time.Millisecond), time.FixedZone("-0700", -7*60*60)), SymmetricString: false, }, "with positive offset": { TimeString: "1985-04-12T23:20:50.52+07:00", TimeValue: time.Date(1985, 4, 12, 23, 20, 50, int(520*time.Millisecond), time.FixedZone("-0700", +7*60*60)), SymmetricString: false, }, "UTC serialize": { TimeString: "1985-04-13T06:20:50.52Z", TimeValue: time.Date(1985, 4, 12, 23, 20, 50, int(520*time.Millisecond), time.FixedZone("-0700", -7*60*60)), SymmetricString: true, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { formattedTimeValue := FormatDateTime(c.TimeValue) // Round Trip time value ensure format and parse are compatible. parsedTimeValue, err := ParseDateTime(formattedTimeValue) if err != nil { t.Fatalf("expected no error, got %v", err) } parsedTimeString, err := ParseDateTime(c.TimeString) if err != nil { t.Fatalf("expected no error, got %v", err) } // Ensure parsing date time from string matches expected time value. if c.SymmetricString { if e, a := c.TimeString, formattedTimeValue; e != a { t.Errorf("expected %v, got %v", e, a) } } if e, a := c.TimeValue, parsedTimeValue; !e.Equal(a) { t.Errorf("expected %v, got %v", e, a) } if e, a := c.TimeValue, parsedTimeString; !e.Equal(a) { t.Errorf("expected %v, got %v", e, a) } }) } } func TestHTTPDate(t *testing.T) { refTime := time.Date(2014, 4, 29, 18, 30, 38, 0, time.UTC) httpDate := FormatHTTPDate(refTime) if e, a := "Tue, 29 Apr 2014 18:30:38 GMT", httpDate; e != a { t.Errorf("expected %v, got %v", e, a) } parseTime, err := ParseHTTPDate(httpDate) if err != nil { t.Fatalf("expected no error, got %v", err) } if e, a := refTime, parseTime; !e.Equal(a) { t.Errorf("expected %v, got %v", e, a) } // UTC serialize date time. refTime = time.Date(2014, 4, 29, 18, 30, 38, 0, time.FixedZone("-700", -7*60*60)) httpDate = FormatHTTPDate(refTime) if e, a := "Wed, 30 Apr 2014 01:30:38 GMT", httpDate; e != a { t.Errorf("expected %v, got %v", e, a) } } func TestParseHTTPDate(t *testing.T) { cases := map[string]struct { date string expect time.Time wantErr bool }{ "with leading zero on day": { date: "Fri, 05 Feb 2021 19:12:15 GMT", expect: time.Date(2021, 2, 5, 19, 12, 15, 0, time.UTC), }, "without leading zero on day": { date: "Fri, 5 Feb 2021 19:12:15 GMT", expect: time.Date(2021, 2, 5, 19, 12, 15, 0, time.UTC), }, "with double digit day": { date: "Fri, 15 Feb 2021 19:12:15 GMT", expect: time.Date(2021, 2, 15, 19, 12, 15, 0, time.UTC), }, "RFC850": { date: "Friday, 05-Feb-21 19:12:15 UTC", expect: time.Date(2021, 2, 5, 19, 12, 15, 0, time.UTC), }, "ANSIC with leading zero on day": { date: "Fri Feb 05 19:12:15 2021", expect: time.Date(2021, 2, 5, 19, 12, 15, 0, time.UTC), }, "ANSIC without leading zero on day": { date: "Fri Feb 5 19:12:15 2021", expect: time.Date(2021, 2, 5, 19, 12, 15, 0, time.UTC), }, "ANSIC with double digit day": { date: "Fri Feb 15 19:12:15 2021", expect: time.Date(2021, 2, 15, 19, 12, 15, 0, time.UTC), }, "invalid time format": { date: "1985-04-12T23:20:50.52Z", wantErr: true, }, "shortened year with double digit day": { date: "Thu, 11 Feb 21 11:04:03 GMT", expect: time.Date(2021, 2, 11, 11, 04, 03, 0, time.UTC), }, "shortened year without leading zero day": { date: "Thu, 5 Feb 21 11:04:03 GMT", expect: time.Date(2021, 2, 5, 11, 04, 03, 0, time.UTC), }, "shortened year with leading zero day": { date: "Thu, 05 Feb 21 11:04:03 GMT", expect: time.Date(2021, 2, 5, 11, 04, 03, 0, time.UTC), }, } for name, tt := range cases { t.Run(name, func(t *testing.T) { result, err := ParseHTTPDate(tt.date) if (err != nil) != tt.wantErr { t.Fatalf("error = %v, wantErr = %v", err, tt.wantErr) } if err != nil { return } if result.IsZero() { t.Fatalf("expected non-zero timestamp") } if tt.expect != result { t.Fatalf("expected %q, got %q", tt.expect, result) } }) } } func TestEpochSeconds(t *testing.T) { cases := []struct { reference time.Time expectedUnix float64 expectedTime time.Time }{ { reference: time.Date(2018, 1, 9, 20, 51, 21, 123399936, time.UTC), expectedUnix: 1515531081.123, expectedTime: time.Date(2018, 1, 9, 20, 51, 21, 1.23e8, time.UTC), }, { reference: time.Date(2018, 1, 9, 20, 51, 21, 1e8, time.UTC), expectedUnix: 1515531081.1, expectedTime: time.Date(2018, 1, 9, 20, 51, 21, 1e8, time.UTC), }, { reference: time.Date(2018, 1, 9, 20, 51, 21, 123567891, time.UTC), expectedUnix: 1515531081.123, expectedTime: time.Date(2018, 1, 9, 20, 51, 21, 1.23e8, time.UTC), }, { reference: time.Unix(0, math.MaxInt64).UTC(), expectedUnix: 9223372036.854, expectedTime: time.Date(2262, 04, 11, 23, 47, 16, 8.54e8, time.UTC), }, { reference: time.Date(2018, 1, 9, 20, 51, 21, 123567891, time.FixedZone("-0700", -7*60*60)), expectedUnix: 1515556281.123, expectedTime: time.Date(2018, 1, 10, 03, 51, 21, 1.23e8, time.UTC), }, } for i, tt := range cases { t.Run(strconv.Itoa(i), func(t *testing.T) { epochSeconds := FormatEpochSeconds(tt.reference) if e, a := tt.expectedUnix, epochSeconds; e != a { t.Errorf("expected %v, got %v", e, a) } parseTime := ParseEpochSeconds(epochSeconds) if e, a := tt.expectedTime, parseTime; !e.Equal(a) { t.Errorf("expected %v, got %v", e, a) } }) } // Check an additional edge that higher precision values are truncated to milliseconds if e, a := time.Date(2018, 1, 9, 20, 51, 21, 1.23e8, time.UTC), ParseEpochSeconds(1515531081.12356); !e.Equal(a) { t.Errorf("expected %v, got %v", e, a) } } smithy-go-1.20.3/transport/000077500000000000000000000000001463735525100155645ustar00rootroot00000000000000smithy-go-1.20.3/transport/http/000077500000000000000000000000001463735525100165435ustar00rootroot00000000000000smithy-go-1.20.3/transport/http/auth.go000066400000000000000000000007641463735525100200420ustar00rootroot00000000000000package http import ( "context" smithy "github.com/aws/smithy-go" "github.com/aws/smithy-go/auth" ) // AuthScheme defines an HTTP authentication scheme. type AuthScheme interface { SchemeID() string IdentityResolver(auth.IdentityResolverOptions) auth.IdentityResolver Signer() Signer } // Signer defines the interface through which HTTP requests are supplemented // with an Identity. type Signer interface { SignRequest(context.Context, *Request, auth.Identity, smithy.Properties) error } smithy-go-1.20.3/transport/http/auth_schemes.go000066400000000000000000000016331463735525100215450ustar00rootroot00000000000000package http import ( "context" smithy "github.com/aws/smithy-go" "github.com/aws/smithy-go/auth" ) // NewAnonymousScheme returns the anonymous HTTP auth scheme. func NewAnonymousScheme() AuthScheme { return &authScheme{ schemeID: auth.SchemeIDAnonymous, signer: &nopSigner{}, } } // authScheme is parameterized to generically implement the exported AuthScheme // interface type authScheme struct { schemeID string signer Signer } var _ AuthScheme = (*authScheme)(nil) func (s *authScheme) SchemeID() string { return s.schemeID } func (s *authScheme) IdentityResolver(o auth.IdentityResolverOptions) auth.IdentityResolver { return o.GetIdentityResolver(s.schemeID) } func (s *authScheme) Signer() Signer { return s.signer } type nopSigner struct{} var _ Signer = (*nopSigner)(nil) func (*nopSigner) SignRequest(context.Context, *Request, auth.Identity, smithy.Properties) error { return nil } smithy-go-1.20.3/transport/http/auth_schemes_test.go000066400000000000000000000010121463735525100225730ustar00rootroot00000000000000package http import ( "testing" "github.com/aws/smithy-go/auth" ) func TestAnonymousScheme(t *testing.T) { expectedID := auth.SchemeIDAnonymous scheme := NewAnonymousScheme() actualID := scheme.SchemeID() if expectedID != actualID { t.Errorf("AnonymousScheme constructor is not producing the correct scheme ID") } var expectedSigner Signer = &nopSigner{} actualSigner := scheme.Signer() if expectedSigner != actualSigner { t.Errorf("AnonymousScheme constructor is not producing the correct signer") } } smithy-go-1.20.3/transport/http/checksum_middleware.go000066400000000000000000000037001463735525100230710ustar00rootroot00000000000000package http import ( "context" "fmt" "github.com/aws/smithy-go/middleware" ) const contentMD5Header = "Content-Md5" // contentMD5Checksum provides a middleware to compute and set // content-md5 checksum for a http request type contentMD5Checksum struct { } // AddContentChecksumMiddleware adds checksum middleware to middleware's // build step. func AddContentChecksumMiddleware(stack *middleware.Stack) error { // This middleware must be executed before request body is set. return stack.Build.Add(&contentMD5Checksum{}, middleware.Before) } // ID returns the identifier for the checksum middleware func (m *contentMD5Checksum) ID() string { return "ContentChecksum" } // HandleBuild adds behavior to compute md5 checksum and add content-md5 header // on http request func (m *contentMD5Checksum) HandleBuild( ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler, ) ( out middleware.BuildOutput, metadata middleware.Metadata, err error, ) { req, ok := in.Request.(*Request) if !ok { return out, metadata, fmt.Errorf("unknown request type %T", req) } // if Content-MD5 header is already present, return if v := req.Header.Get(contentMD5Header); len(v) != 0 { return next.HandleBuild(ctx, in) } // fetch the request stream. stream := req.GetStream() // compute checksum if payload is explicit if stream != nil { if !req.IsStreamSeekable() { return out, metadata, fmt.Errorf( "unseekable stream is not supported for computing md5 checksum") } v, err := computeMD5Checksum(stream) if err != nil { return out, metadata, fmt.Errorf("error computing md5 checksum, %w", err) } // reset the request stream if err := req.RewindStream(); err != nil { return out, metadata, fmt.Errorf( "error rewinding request stream after computing md5 checksum, %w", err) } // set the 'Content-MD5' header req.Header.Set(contentMD5Header, string(v)) } // set md5 header value return next.HandleBuild(ctx, in) } smithy-go-1.20.3/transport/http/checksum_middleware_test.go000066400000000000000000000040511463735525100241300ustar00rootroot00000000000000package http import ( "bytes" "context" "io" "strings" "testing" smithyio "github.com/aws/smithy-go/io" "github.com/aws/smithy-go/middleware" ) func TestChecksumMiddleware(t *testing.T) { cases := map[string]struct { payload io.Reader expectedPayloadLength int64 expectedMD5Checksum string expectError string }{ "empty body": { payload: smithyio.ReadSeekNopCloser{ ReadSeeker: bytes.NewReader([]byte(``)), }, expectedPayloadLength: 0, expectedMD5Checksum: "1B2M2Y8AsgTpgAmY7PhCfg==", }, "standard req body": { payload: smithyio.ReadSeekNopCloser{ ReadSeeker: bytes.NewReader([]byte(`abc`)), }, expectedPayloadLength: 3, expectedMD5Checksum: "kAFQmDzST7DWlj99KOF/cg==", }, "nil body": {}, "unseekable payload": { payload: bytes.NewBuffer([]byte(`xyz`)), expectError: "unseekable stream is not supported", }, } for name, c := range cases { t.Run(name, func(t *testing.T) { var err error req := NewStackRequest().(*Request) req, err = req.SetStream(c.payload) if err != nil { t.Fatalf("error setting request stream") } m := contentMD5Checksum{} _, _, err = m.HandleBuild(context.Background(), middleware.BuildInput{Request: req}, nopBuildHandler, ) if len(c.expectError) != 0 { if err == nil { t.Fatalf("expect error, got none") } if e, a := c.expectError, err.Error(); !strings.Contains(a, e) { t.Fatalf("expect error to contain %q, got %v", e, a) } return } else if err != nil { t.Fatalf("expect no error, got %v", err) } if e, a := c.expectedMD5Checksum, req.Header.Get(contentMD5Header); e != a { t.Errorf("expect md5 checksum : %v, got %v", e, a) } size, ok, err := req.StreamLength() if err != nil { t.Fatalf("error fetching request stream length") } if !ok { t.Fatalf("request stream is not seekable") } if e, a := c.expectedPayloadLength, size; e != a { t.Fatalf("expected request stream content length to be %v, got length %v", e, a) } }) } } smithy-go-1.20.3/transport/http/client.go000066400000000000000000000066461463735525100203640ustar00rootroot00000000000000package http import ( "context" "fmt" "net/http" smithy "github.com/aws/smithy-go" "github.com/aws/smithy-go/middleware" ) // ClientDo provides the interface for custom HTTP client implementations. type ClientDo interface { Do(*http.Request) (*http.Response, error) } // ClientDoFunc provides a helper to wrap a function as an HTTP client for // round tripping requests. type ClientDoFunc func(*http.Request) (*http.Response, error) // Do will invoke the underlying func, returning the result. func (fn ClientDoFunc) Do(r *http.Request) (*http.Response, error) { return fn(r) } // ClientHandler wraps a client that implements the HTTP Do method. Standard // implementation is http.Client. type ClientHandler struct { client ClientDo } // NewClientHandler returns an initialized middleware handler for the client. func NewClientHandler(client ClientDo) ClientHandler { return ClientHandler{ client: client, } } // Handle implements the middleware Handler interface, that will invoke the // underlying HTTP client. Requires the input to be a Smithy *Request. Returns // a smithy *Response, or error if the request failed. func (c ClientHandler) Handle(ctx context.Context, input interface{}) ( out interface{}, metadata middleware.Metadata, err error, ) { req, ok := input.(*Request) if !ok { return nil, metadata, fmt.Errorf("expect Smithy http.Request value as input, got unsupported type %T", input) } builtRequest := req.Build(ctx) if err := ValidateEndpointHost(builtRequest.Host); err != nil { return nil, metadata, err } resp, err := c.client.Do(builtRequest) if resp == nil { // Ensure a http response value is always present to prevent unexpected // panics. resp = &http.Response{ Header: http.Header{}, Body: http.NoBody, } } if err != nil { err = &RequestSendError{Err: err} // Override the error with a context canceled error, if that was canceled. select { case <-ctx.Done(): err = &smithy.CanceledError{Err: ctx.Err()} default: } } // HTTP RoundTripper *should* close the request body. But this may not happen in a timely manner. // So instead Smithy *Request Build wraps the body to be sent in a safe closer that will clear the // stream reference so that it can be safely reused. if builtRequest.Body != nil { _ = builtRequest.Body.Close() } return &Response{Response: resp}, metadata, err } // RequestSendError provides a generic request transport error. This error // should wrap errors making HTTP client requests. // // The ClientHandler will wrap the HTTP client's error if the client request // fails, and did not fail because of context canceled. type RequestSendError struct { Err error } // ConnectionError returns that the error is related to not being able to send // the request, or receive a response from the service. func (e *RequestSendError) ConnectionError() bool { return true } // Unwrap returns the underlying error, if there was one. func (e *RequestSendError) Unwrap() error { return e.Err } func (e *RequestSendError) Error() string { return fmt.Sprintf("request send failed, %v", e.Err) } // NopClient provides a client that ignores the request, and returns an empty // successful HTTP response value. type NopClient struct{} // Do ignores the request and returns a 200 status empty response. func (NopClient) Do(r *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: 200, Header: http.Header{}, Body: http.NoBody, }, nil } smithy-go-1.20.3/transport/http/client_test.go000066400000000000000000000042041463735525100214070ustar00rootroot00000000000000package http import ( "context" "errors" "fmt" "net/http" "testing" smithy "github.com/aws/smithy-go" ) func TestClientHandler_Handle(t *testing.T) { cases := map[string]struct { Context context.Context Client ClientDo ExpectErr func(error) error }{ "no error": { Context: context.Background(), Client: ClientDoFunc(func(*http.Request) (*http.Response, error) { return &http.Response{}, nil }), }, "send error": { Context: context.Background(), Client: ClientDoFunc(func(*http.Request) (*http.Response, error) { return nil, fmt.Errorf("some error") }), ExpectErr: func(err error) error { var sendError *RequestSendError if !errors.As(err, &sendError) { return fmt.Errorf("expect error to be %T, %v", sendError, err) } var cancelError *smithy.CanceledError if errors.As(err, &cancelError) { return fmt.Errorf("expect error to not be %T, %v", cancelError, err) } return nil }, }, "canceled error": { Context: func() context.Context { ctx, fn := context.WithCancel(context.Background()) fn() return ctx }(), Client: ClientDoFunc(func(*http.Request) (*http.Response, error) { return nil, fmt.Errorf("some error") }), ExpectErr: func(err error) error { var sendError *RequestSendError if errors.As(err, &sendError) { return fmt.Errorf("expect error to not be %T, %v", sendError, err) } var cancelError *smithy.CanceledError if !errors.As(err, &cancelError) { return fmt.Errorf("expect error to be %T, %v", cancelError, err) } return nil }, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { handler := NewClientHandler(c.Client) resp, _, err := handler.Handle(c.Context, NewStackRequest()) if c.ExpectErr != nil { if err == nil { t.Fatalf("expect error, got none") } if err = c.ExpectErr(err); err != nil { t.Fatalf("expect error match failed, %v", err) } return } if err != nil { t.Fatalf("expect no error, got %v", err) } if _, ok := resp.(*Response); !ok { t.Fatalf("expect Response type, got %T", resp) } }) } } smithy-go-1.20.3/transport/http/deserialize_example_test.go000066400000000000000000000046401463735525100241500ustar00rootroot00000000000000package http import ( "context" "fmt" "net/http" "os" "strconv" "github.com/aws/smithy-go/middleware" ) func ExampleResponse_deserializeMiddleware() { // Create the stack and provide the function that will create a new Request // when the SerializeStep is invoked. stack := middleware.NewStack("deserialize example", NewStackRequest) type Output struct { FooName string BarCount int } // Add a Deserialize middleware that will extract the RawResponse and // deserialize into the target output type. stack.Deserialize.Add(middleware.DeserializeMiddlewareFunc("example deserialize", func(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) ( out middleware.DeserializeOutput, metadata middleware.Metadata, err error, ) { out, metadata, err = next.HandleDeserialize(ctx, in) if err != nil { return middleware.DeserializeOutput{}, metadata, err } metadata.Set("example-meta", "meta-value") rawResp := out.RawResponse.(*Response) out.Result = &Output{ FooName: rawResp.Header.Get("foo-name"), BarCount: func() int { v, _ := strconv.Atoi(rawResp.Header.Get("bar-count")) return v }(), } return out, metadata, nil }), middleware.After, ) // Mock example handler taking the request input and returning a response mockHandler := middleware.HandlerFunc(func(ctx context.Context, in interface{}) ( output interface{}, metadata middleware.Metadata, err error, ) { resp := &http.Response{ StatusCode: 200, Header: http.Header{}, } resp.Header.Set("foo-name", "abc") resp.Header.Set("bar-count", "123") // The handler's returned response will be available as the // DeserializeOutput.RawResponse field. return &Response{ Response: resp, }, metadata, nil }) // Use the stack to decorate the handler then invoke the decorated handler // with the inputs. handler := middleware.DecorateHandler(mockHandler, stack) result, metadata, err := handler.Handle(context.Background(), struct{}{}) if err != nil { fmt.Fprintf(os.Stderr, "failed to call operation, %v", err) return } // Cast the result returned by the handler to the expected Output type. res := result.(*Output) fmt.Println("FooName", res.FooName) fmt.Println("BarCount", res.BarCount) fmt.Println("Metadata:", "example-meta:", metadata.Get("example-meta")) // Output: // FooName abc // BarCount 123 // Metadata: example-meta: meta-value } smithy-go-1.20.3/transport/http/doc.go000066400000000000000000000002301463735525100176320ustar00rootroot00000000000000/* Package http provides the HTTP transport client and request/response types needed to round trip API operation calls with an service. */ package http smithy-go-1.20.3/transport/http/headerlist.go000066400000000000000000000077151463735525100212300ustar00rootroot00000000000000package http import ( "fmt" "strconv" "strings" "unicode" ) func splitHeaderListValues(vs []string, splitFn func(string) ([]string, error)) ([]string, error) { values := make([]string, 0, len(vs)) for i := 0; i < len(vs); i++ { parts, err := splitFn(vs[i]) if err != nil { return nil, err } values = append(values, parts...) } return values, nil } // SplitHeaderListValues attempts to split the elements of the slice by commas, // and return a list of all values separated. Returns error if unable to // separate the values. func SplitHeaderListValues(vs []string) ([]string, error) { return splitHeaderListValues(vs, quotedCommaSplit) } func quotedCommaSplit(v string) (parts []string, err error) { v = strings.TrimSpace(v) expectMore := true for i := 0; i < len(v); i++ { if unicode.IsSpace(rune(v[i])) { continue } expectMore = false // leading space in part is ignored. // Start of value must be non-space, or quote. // // - If quote, enter quoted mode, find next non-escaped quote to // terminate the value. // - Otherwise, find next comma to terminate value. remaining := v[i:] var value string var valueLen int if remaining[0] == '"' { //------------------------------ // Quoted value //------------------------------ var j int var skipQuote bool for j += 1; j < len(remaining); j++ { if remaining[j] == '\\' || (remaining[j] != '\\' && skipQuote) { skipQuote = !skipQuote continue } if remaining[j] == '"' { break } } if j == len(remaining) || j == 1 { return nil, fmt.Errorf("value %v missing closing double quote", remaining) } valueLen = j + 1 tail := remaining[valueLen:] var k int for ; k < len(tail); k++ { if !unicode.IsSpace(rune(tail[k])) && tail[k] != ',' { return nil, fmt.Errorf("value %v has non-space trailing characters", remaining) } if tail[k] == ',' { expectMore = true break } } value = remaining[:valueLen] value, err = strconv.Unquote(value) if err != nil { return nil, fmt.Errorf("failed to unquote value %v, %w", value, err) } // Pad valueLen to include trailing space(s) so `i` is updated correctly. valueLen += k } else { //------------------------------ // Unquoted value //------------------------------ // Index of the next comma is the length of the value, or end of string. valueLen = strings.Index(remaining, ",") if valueLen != -1 { expectMore = true } else { valueLen = len(remaining) } value = strings.TrimSpace(remaining[:valueLen]) } i += valueLen parts = append(parts, value) } if expectMore { parts = append(parts, "") } return parts, nil } // SplitHTTPDateTimestampHeaderListValues attempts to split the HTTP-Date // timestamp values in the slice by commas, and return a list of all values // separated. The split is aware of the HTTP-Date timestamp format, and will skip // comma within the timestamp value. Returns an error if unable to split the // timestamp values. func SplitHTTPDateTimestampHeaderListValues(vs []string) ([]string, error) { return splitHeaderListValues(vs, splitHTTPDateHeaderValue) } func splitHTTPDateHeaderValue(v string) ([]string, error) { if n := strings.Count(v, ","); n <= 1 { // Nothing to do if only contains a no, or single HTTPDate value return []string{v}, nil } else if n%2 == 0 { return nil, fmt.Errorf("invalid timestamp HTTPDate header comma separations, %q", v) } var parts []string var i, j int var doSplit bool for ; i < len(v); i++ { if v[i] == ',' { if doSplit { doSplit = false parts = append(parts, strings.TrimSpace(v[j:i])) j = i + 1 } else { // Skip the first comma in the timestamp value since that // separates the day from the rest of the timestamp. // // Tue, 17 Dec 2019 23:48:18 GMT doSplit = true } } } // Add final part if j < len(v) { parts = append(parts, strings.TrimSpace(v[j:])) } return parts, nil } smithy-go-1.20.3/transport/http/headerlist_test.go000066400000000000000000000072731463735525100222660ustar00rootroot00000000000000package http import ( "reflect" "strings" "testing" ) func TestSplitHeaderListValues(t *testing.T) { cases := map[string]struct { Values []string Expect []string ExpectErr string }{ "no split": { Values: []string{ "abc", "123", "hello", }, Expect: []string{ "abc", "123", "hello", }, }, "with split": { Values: []string{ "a, b, c, 1, 2, 3", }, Expect: []string{ "a", "b", "c", "1", "2", "3", }, }, "mixed with split": { Values: []string{ "abc", "1, 23", "hello,world", }, Expect: []string{ "abc", "1", "23", "hello", "world", }, }, "empty values": { Values: []string{ "", ", 1, 23, hello,world", }, Expect: []string{ "", "", "1", "23", "hello", "world", }, }, "quoted values": { Values: []string{ `abc, 123, "abc,123", "456,efg"`, }, Expect: []string{ "abc", "123", "abc,123", "456,efg", }, }, "quoted escaped values": { Values: []string{ `abc,123, "abc,123" , " \"abc , 123\" " , "\\456,efg\\b" ,`, }, Expect: []string{ "abc", "123", "abc,123", " \"abc , 123\" ", "\\456,efg\\b", "", }, }, "wrapping space": { Values: []string{ ` abc,123, "abc,123" , " \"abc , 123\" " , "\\456,efg\\b" ,`, }, Expect: []string{ "abc", "123", "abc,123", " \"abc , 123\" ", "\\456,efg\\b", "", }, }, "trailing empty value": { Values: []string{ `, , `, }, Expect: []string{ "", "", "", }, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { actual, err := SplitHeaderListValues(c.Values) if err != nil { t.Fatalf("expect no error, %v", err) } if !reflect.DeepEqual(c.Expect, actual) { t.Errorf("%v != %v", c.Expect, actual) } }) } } func TestSplitHTTPDateTimestampHeaderListValues(t *testing.T) { cases := map[string]struct { Values []string Expect []string ExpectErr string }{ "no split": { Values: []string{ "Mon, 16 Dec 2019 23:48:18 GMT", }, Expect: []string{ "Mon, 16 Dec 2019 23:48:18 GMT", }, }, "with split": { Values: []string{ "Mon, 16 Dec 2019 23:48:18 GMT, Tue, 17 Dec 2019 23:48:18 GMT", }, Expect: []string{ "Mon, 16 Dec 2019 23:48:18 GMT", "Tue, 17 Dec 2019 23:48:18 GMT", }, }, "mixed with split": { Values: []string{ "Sun, 15 Dec 2019 23:48:18 GMT", "Mon, 16 Dec 2019 23:48:18 GMT, Tue, 17 Dec 2019 23:48:18 GMT", "Wed, 18 Dec 2019 23:48:18 GMT", }, Expect: []string{ "Sun, 15 Dec 2019 23:48:18 GMT", "Mon, 16 Dec 2019 23:48:18 GMT", "Tue, 17 Dec 2019 23:48:18 GMT", "Wed, 18 Dec 2019 23:48:18 GMT", }, }, "empty values": { Values: []string{ "", "Mon, 16 Dec 2019 23:48:18 GMT, Tue, 17 Dec 2019 23:48:18 GMT", "Wed, 18 Dec 2019 23:48:18 GMT", }, Expect: []string{ "", "Mon, 16 Dec 2019 23:48:18 GMT", "Tue, 17 Dec 2019 23:48:18 GMT", "Wed, 18 Dec 2019 23:48:18 GMT", }, }, "bad format": { Values: []string{ "Mon, 16 Dec 2019 23:48:18 GMT, , Tue, 17 Dec 2019 23:48:18 GMT", }, ExpectErr: "invalid timestamp", }, } for name, c := range cases { t.Run(name, func(t *testing.T) { actual, err := SplitHTTPDateTimestampHeaderListValues(c.Values) if len(c.ExpectErr) != 0 { if err == nil { t.Fatalf("expect error, got none") } if e, a := c.ExpectErr, err.Error(); !strings.Contains(a, e) { t.Fatalf("expect error to contain %v, got %v", e, a) } return } if err != nil { t.Fatalf("expect no error, got %v", err) } if !reflect.DeepEqual(c.Expect, actual) { t.Errorf("%v != %v", c.Expect, actual) } }) } } smithy-go-1.20.3/transport/http/host.go000066400000000000000000000037451463735525100200600ustar00rootroot00000000000000package http import ( "fmt" "net" "strconv" "strings" ) // ValidateEndpointHost validates that the host string passed in is a valid RFC // 3986 host. Returns error if the host is not valid. func ValidateEndpointHost(host string) error { var errors strings.Builder var hostname string var port string var err error if strings.Contains(host, ":") { hostname, port, err = net.SplitHostPort(host) if err != nil { errors.WriteString(fmt.Sprintf("\n endpoint %v, failed to parse, got ", host)) errors.WriteString(err.Error()) } if !ValidPortNumber(port) { errors.WriteString(fmt.Sprintf("port number should be in range [0-65535], got %v", port)) } } else { hostname = host } labels := strings.Split(hostname, ".") for i, label := range labels { if i == len(labels)-1 && len(label) == 0 { // Allow trailing dot for FQDN hosts. continue } if !ValidHostLabel(label) { errors.WriteString("\nendpoint host domain labels must match \"[a-zA-Z0-9-]{1,63}\", but found: ") errors.WriteString(label) } } if len(hostname) == 0 && len(port) != 0 { errors.WriteString("\nendpoint host with port must not be empty") } if len(hostname) > 255 { errors.WriteString(fmt.Sprintf("\nendpoint host must be less than 255 characters, but was %d", len(hostname))) } if len(errors.String()) > 0 { return fmt.Errorf("invalid endpoint host%s", errors.String()) } return nil } // ValidPortNumber returns whether the port is valid RFC 3986 port. func ValidPortNumber(port string) bool { i, err := strconv.Atoi(port) if err != nil { return false } if i < 0 || i > 65535 { return false } return true } // ValidHostLabel returns whether the label is a valid RFC 3986 host abel. func ValidHostLabel(label string) bool { if l := len(label); l == 0 || l > 63 { return false } for _, r := range label { switch { case r >= '0' && r <= '9': case r >= 'A' && r <= 'Z': case r >= 'a' && r <= 'z': case r == '-': default: return false } } return true } smithy-go-1.20.3/transport/http/host_test.go000066400000000000000000000052151463735525100211110ustar00rootroot00000000000000package http import ( "strconv" "testing" ) func TestValidPortNumber(t *testing.T) { cases := []struct { Input string Valid bool }{ {Input: "123", Valid: true}, {Input: "123.0", Valid: false}, {Input: "-123", Valid: false}, {Input: "65536", Valid: false}, {Input: "0", Valid: true}, } for i, c := range cases { t.Run(strconv.Itoa(i), func(t *testing.T) { valid := ValidPortNumber(c.Input) if e, a := c.Valid, valid; e != a { t.Errorf("expect valid %v, got %v", e, a) } }) } } func TestValidHostLabel(t *testing.T) { cases := []struct { Input string Valid bool }{ {Input: "abc123", Valid: true}, {Input: "123", Valid: true}, {Input: "abc", Valid: true}, {Input: "123-abc", Valid: true}, {Input: "{thing}-abc", Valid: false}, {Input: "abc.123", Valid: false}, {Input: "abc/123", Valid: false}, {Input: "012345678901234567890123456789012345678901234567890123456789123", Valid: true}, {Input: "0123456789012345678901234567890123456789012345678901234567891234", Valid: false}, {Input: "", Valid: false}, } for i, c := range cases { t.Run(strconv.Itoa(i), func(t *testing.T) { valid := ValidHostLabel(c.Input) if e, a := c.Valid, valid; e != a { t.Errorf("expect valid %v, got %v", e, a) } }) } } func TestValidateEndpointHostHandler(t *testing.T) { cases := map[string]struct { Input string Valid bool }{ "valid host": {Input: "abc.123", Valid: true}, "fqdn host": {Input: "abc.123.", Valid: true}, "empty label": {Input: "abc..", Valid: false}, "max host len": { Input: "123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.12345", Valid: true, }, "too long host": { Input: "123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456", Valid: false, }, "valid host with port number": {Input: "abd.123:1234", Valid: true}, "valid host with invalid port number": {Input: "abc.123:99999", Valid: false}, "empty host with port number": {Input: ":1234", Valid: false}, "valid host with empty port number": {Input: "abc.123:", Valid: false}, } for name, c := range cases { t.Run(name, func(t *testing.T) { err := ValidateEndpointHost(c.Input) if e, a := c.Valid, err == nil; e != a { t.Errorf("expect valid %v, got %v, %v", e, a, err) } }) } } smithy-go-1.20.3/transport/http/identity_test.go000066400000000000000000000006561463735525100217710ustar00rootroot00000000000000package http import ( "context" "testing" smithy "github.com/aws/smithy-go" "github.com/aws/smithy-go/auth" ) func TestIdentity(t *testing.T) { var expected auth.Identity = &auth.AnonymousIdentity{} resolver := auth.AnonymousIdentityResolver{} actual, _ := resolver.GetIdentity(context.TODO(), smithy.Properties{}) if expected != actual { t.Errorf("Anonymous identity resolver does not produce correct identity") } }smithy-go-1.20.3/transport/http/internal/000077500000000000000000000000001463735525100203575ustar00rootroot00000000000000smithy-go-1.20.3/transport/http/internal/io/000077500000000000000000000000001463735525100207665ustar00rootroot00000000000000smithy-go-1.20.3/transport/http/internal/io/safe.go000066400000000000000000000042361463735525100222400ustar00rootroot00000000000000package io import ( "io" "sync" ) // NewSafeReadCloser returns a new safeReadCloser that wraps readCloser. func NewSafeReadCloser(readCloser io.ReadCloser) io.ReadCloser { sr := &safeReadCloser{ readCloser: readCloser, } if _, ok := readCloser.(io.WriterTo); ok { return &safeWriteToReadCloser{safeReadCloser: sr} } return sr } // safeWriteToReadCloser wraps a safeReadCloser but exposes a WriteTo interface implementation. This will panic // if the underlying io.ReadClose does not support WriteTo. Use NewSafeReadCloser to ensure the proper handling of this // type. type safeWriteToReadCloser struct { *safeReadCloser } // WriteTo implements the io.WriteTo interface. func (r *safeWriteToReadCloser) WriteTo(w io.Writer) (int64, error) { r.safeReadCloser.mtx.Lock() defer r.safeReadCloser.mtx.Unlock() if r.safeReadCloser.closed { return 0, io.EOF } return r.safeReadCloser.readCloser.(io.WriterTo).WriteTo(w) } // safeReadCloser wraps a io.ReadCloser and presents an io.ReadCloser interface. When Close is called on safeReadCloser // the underlying Close method will be executed, and then the reference to the reader will be dropped. This type // is meant to be used with the net/http library which will retain a reference to the request body for the lifetime // of a goroutine connection. Wrapping in this manner will ensure that no data race conditions are falsely reported. // This type is thread-safe. type safeReadCloser struct { readCloser io.ReadCloser closed bool mtx sync.Mutex } // Read reads up to len(p) bytes into p from the underlying read. If the reader is closed io.EOF will be returned. func (r *safeReadCloser) Read(p []byte) (n int, err error) { r.mtx.Lock() defer r.mtx.Unlock() if r.closed { return 0, io.EOF } return r.readCloser.Read(p) } // Close calls the underlying io.ReadCloser's Close method, removes the reference to the reader, and returns any error // reported from Close. Subsequent calls to Close will always return a nil error. func (r *safeReadCloser) Close() error { r.mtx.Lock() defer r.mtx.Unlock() if r.closed { return nil } r.closed = true rc := r.readCloser r.readCloser = nil return rc.Close() } smithy-go-1.20.3/transport/http/internal/io/safe_test.go000066400000000000000000000075351463735525100233040ustar00rootroot00000000000000package io import ( "bytes" "errors" "fmt" "io" "testing" ) type mockReadCloser struct { ReadFn func([]byte) (int, error) CloseFn func() error } func (m *mockReadCloser) Read(p []byte) (n int, err error) { if m.ReadFn == nil { return len(p), nil } return m.ReadFn(p) } func (m *mockReadCloser) Close() error { if m.CloseFn == nil { return nil } return m.CloseFn() } type mockWriteTo struct { mockReadCloser WriteToFn func(io.Writer) (int64, error) } func (m *mockWriteTo) WriteTo(w io.Writer) (int64, error) { if m.WriteToFn == nil { return 0, nil } return m.WriteToFn(w) } func TestNewSafeReadCloser(t *testing.T) { cases := map[string]struct { ReadCloser io.ReadCloser ReadTest func(*testing.T, *safeReadCloser) CloseTest func(*testing.T, *safeReadCloser) }{ "success read and close": { ReadCloser: &mockReadCloser{ ReadFn: func(bytes []byte) (int, error) { bytes[0], bytes[1], bytes[2] = 'f', 'o', 'o' return 3, nil }, }, ReadTest: func(t *testing.T, closer *safeReadCloser) { t.Helper() bs := make([]byte, 3) read, err := closer.Read(bs) if err != nil { t.Errorf("expect no error, got %v", err) } if e, a := "foo", string(bs[:read]); e != a { t.Errorf("expect %v, got %v", e, a) } }, CloseTest: func(t *testing.T, closer *safeReadCloser) { t.Helper() if err := closer.Close(); err != nil { t.Errorf("expect no error, got %v", err) } }, }, "error read": { ReadCloser: &mockReadCloser{ ReadFn: func(bytes []byte) (int, error) { return 0, io.ErrUnexpectedEOF }, }, ReadTest: func(t *testing.T, closer *safeReadCloser) { t.Helper() _, err := closer.Read([]byte{}) if err == nil { t.Errorf("expect error, got nil") } if !errors.Is(err, io.ErrUnexpectedEOF) { t.Errorf("expect error to be type %T, got %T", io.ErrUnexpectedEOF, err) } }, }, "error close": { ReadCloser: &mockReadCloser{ CloseFn: func() error { return fmt.Errorf("foobar error") }, }, CloseTest: func(t *testing.T, closer *safeReadCloser) { t.Helper() if err := closer.Close(); err == nil { t.Error("expect close error, got nil") } else if e, a := "foobar error", err.Error(); e != a { t.Errorf("expect %v, got %v", e, a) } }, }, } for name, tt := range cases { t.Run(name, func(t *testing.T) { sr := NewSafeReadCloser(tt.ReadCloser).(*safeReadCloser) if e, a := false, sr.closed; e != a { t.Errorf("expect %v, got %v", e, a) } if tt.ReadTest != nil { tt.ReadTest(t, sr) } if tt.CloseTest != nil { tt.CloseTest(t, sr) } else { sr.Close() } if e, a := true, sr.closed; e != a { t.Errorf("expect %v, got %v", e, a) } if sr.readCloser != nil { t.Errorf("expect reader to be nil after Close") } if err := sr.Close(); err != nil { t.Errorf("expect subsequent Close returns to be nil, got %v", err) } }) } } func TestNewSafeReadCloser_WriteTo(t *testing.T) { { rc := &mockWriteTo{} writeToReadCloser := NewSafeReadCloser(rc).(*safeWriteToReadCloser) _, err := writeToReadCloser.WriteTo(nil) if err != nil { t.Errorf("expect no error, got %v", err) } err = writeToReadCloser.Close() if err != nil { t.Errorf("expect no error, got %v", err) } _, err = writeToReadCloser.WriteTo(nil) if err != io.EOF { t.Errorf("expect %T, got %T", io.EOF, err) } } { rc := &mockWriteTo{ WriteToFn: func(writer io.Writer) (int64, error) { write, err := writer.Write([]byte("foo")) return int64(write), err }, } writeToReadCloser := NewSafeReadCloser(rc).(*safeWriteToReadCloser) var buf bytes.Buffer _, err := writeToReadCloser.WriteTo(&buf) if err != nil { t.Errorf("expect no error, got %v", err) } if e, a := "foo", buf.String(); e != a { t.Errorf("expect %v, got %v", e, a) } } } smithy-go-1.20.3/transport/http/md5_checksum.go000066400000000000000000000011471463735525100214440ustar00rootroot00000000000000package http import ( "crypto/md5" "encoding/base64" "fmt" "io" ) // computeMD5Checksum computes base64 md5 checksum of an io.Reader's contents. // Returns the byte slice of md5 checksum and an error. func computeMD5Checksum(r io.Reader) ([]byte, error) { h := md5.New() // copy errors may be assumed to be from the body. _, err := io.Copy(h, r) if err != nil { return nil, fmt.Errorf("failed to read body: %w", err) } // encode the md5 checksum in base64. sum := h.Sum(nil) sum64 := make([]byte, base64.StdEncoding.EncodedLen(len(sum))) base64.StdEncoding.Encode(sum64, sum) return sum64, nil } smithy-go-1.20.3/transport/http/middleware_close_response_body.go000066400000000000000000000050451463735525100253330ustar00rootroot00000000000000package http import ( "context" "github.com/aws/smithy-go/logging" "github.com/aws/smithy-go/middleware" "io" "io/ioutil" ) // AddErrorCloseResponseBodyMiddleware adds the middleware to automatically // close the response body of an operation request if the request response // failed. func AddErrorCloseResponseBodyMiddleware(stack *middleware.Stack) error { return stack.Deserialize.Insert(&errorCloseResponseBodyMiddleware{}, "OperationDeserializer", middleware.Before) } type errorCloseResponseBodyMiddleware struct{} func (*errorCloseResponseBodyMiddleware) ID() string { return "ErrorCloseResponseBody" } func (m *errorCloseResponseBodyMiddleware) HandleDeserialize( ctx context.Context, input middleware.DeserializeInput, next middleware.DeserializeHandler, ) ( output middleware.DeserializeOutput, metadata middleware.Metadata, err error, ) { out, metadata, err := next.HandleDeserialize(ctx, input) if err != nil { if resp, ok := out.RawResponse.(*Response); ok && resp != nil && resp.Body != nil { // Consume the full body to prevent TCP connection resets on some platforms _, _ = io.Copy(ioutil.Discard, resp.Body) // Do not validate that the response closes successfully. resp.Body.Close() } } return out, metadata, err } // AddCloseResponseBodyMiddleware adds the middleware to automatically close // the response body of an operation request, after the response had been // deserialized. func AddCloseResponseBodyMiddleware(stack *middleware.Stack) error { return stack.Deserialize.Insert(&closeResponseBody{}, "OperationDeserializer", middleware.Before) } type closeResponseBody struct{} func (*closeResponseBody) ID() string { return "CloseResponseBody" } func (m *closeResponseBody) HandleDeserialize( ctx context.Context, input middleware.DeserializeInput, next middleware.DeserializeHandler, ) ( output middleware.DeserializeOutput, metadata middleware.Metadata, err error, ) { out, metadata, err := next.HandleDeserialize(ctx, input) if err != nil { return out, metadata, err } if resp, ok := out.RawResponse.(*Response); ok { // Consume the full body to prevent TCP connection resets on some platforms _, copyErr := io.Copy(ioutil.Discard, resp.Body) if copyErr != nil { middleware.GetLogger(ctx).Logf(logging.Warn, "failed to discard remaining HTTP response body, this may affect connection reuse") } closeErr := resp.Body.Close() if closeErr != nil { middleware.GetLogger(ctx).Logf(logging.Warn, "failed to close HTTP response body, this may affect connection reuse") } } return out, metadata, err } smithy-go-1.20.3/transport/http/middleware_content_length.go000066400000000000000000000052171463735525100243070ustar00rootroot00000000000000package http import ( "context" "fmt" "github.com/aws/smithy-go/middleware" ) // ComputeContentLength provides a middleware to set the content-length // header for the length of a serialize request body. type ComputeContentLength struct { } // AddComputeContentLengthMiddleware adds ComputeContentLength to the middleware // stack's Build step. func AddComputeContentLengthMiddleware(stack *middleware.Stack) error { return stack.Build.Add(&ComputeContentLength{}, middleware.After) } // ID returns the identifier for the ComputeContentLength. func (m *ComputeContentLength) ID() string { return "ComputeContentLength" } // HandleBuild adds the length of the serialized request to the HTTP header // if the length can be determined. func (m *ComputeContentLength) HandleBuild( ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler, ) ( out middleware.BuildOutput, metadata middleware.Metadata, err error, ) { req, ok := in.Request.(*Request) if !ok { return out, metadata, fmt.Errorf("unknown request type %T", req) } // do nothing if request content-length was set to 0 or above. if req.ContentLength >= 0 { return next.HandleBuild(ctx, in) } // attempt to compute stream length if n, ok, err := req.StreamLength(); err != nil { return out, metadata, fmt.Errorf( "failed getting length of request stream, %w", err) } else if ok { req.ContentLength = n } return next.HandleBuild(ctx, in) } // validateContentLength provides a middleware to validate the content-length // is valid (greater than zero), for the serialized request payload. type validateContentLength struct{} // ValidateContentLengthHeader adds middleware that validates request content-length // is set to value greater than zero. func ValidateContentLengthHeader(stack *middleware.Stack) error { return stack.Build.Add(&validateContentLength{}, middleware.After) } // ID returns the identifier for the ComputeContentLength. func (m *validateContentLength) ID() string { return "ValidateContentLength" } // HandleBuild adds the length of the serialized request to the HTTP header // if the length can be determined. func (m *validateContentLength) HandleBuild( ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler, ) ( out middleware.BuildOutput, metadata middleware.Metadata, err error, ) { req, ok := in.Request.(*Request) if !ok { return out, metadata, fmt.Errorf("unknown request type %T", req) } // if request content-length was set to less than 0, return an error if req.ContentLength < 0 { return out, metadata, fmt.Errorf( "content length for payload is required and must be at least 0") } return next.HandleBuild(ctx, in) } smithy-go-1.20.3/transport/http/middleware_content_length_test.go000066400000000000000000000113611463735525100253430ustar00rootroot00000000000000package http import ( "bytes" "context" "fmt" "io" "strings" "testing" "github.com/aws/smithy-go/middleware" ) func TestContentLengthMiddleware(t *testing.T) { cases := map[string]struct { Stream io.Reader ExpectNilStream bool ExpectLen int64 ExpectErr string }{ // Cases "bytes.Reader": { Stream: bytes.NewReader(make([]byte, 10)), ExpectLen: 10, ExpectNilStream: false, }, "bytes.Buffer": { Stream: bytes.NewBuffer(make([]byte, 10)), ExpectLen: 10, ExpectNilStream: false, }, "strings.Reader": { Stream: strings.NewReader("hello"), ExpectLen: 5, ExpectNilStream: false, }, "empty stream": { Stream: strings.NewReader(""), ExpectLen: 0, ExpectNilStream: false, }, "empty stream bytes": { Stream: bytes.NewReader([]byte{}), ExpectLen: 0, ExpectNilStream: false, }, "nil stream": { ExpectLen: 0, ExpectNilStream: true, }, "un-seekable and no length": { Stream: &basicReader{buf: make([]byte, 10)}, ExpectLen: -1, ExpectNilStream: false, }, "with error": { Stream: &errorSecondSeekableReader{err: fmt.Errorf("seek failed")}, ExpectErr: "seek failed", ExpectLen: -1, ExpectNilStream: false, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { var err error req := NewStackRequest().(*Request) req, err = req.SetStream(c.Stream) if err != nil { t.Fatalf("expect to set stream, %v", err) } var updatedRequest *Request var m ComputeContentLength _, _, err = m.HandleBuild(context.Background(), middleware.BuildInput{Request: req}, middleware.BuildHandlerFunc(func(ctx context.Context, input middleware.BuildInput) ( out middleware.BuildOutput, metadata middleware.Metadata, err error) { updatedRequest = input.Request.(*Request) return out, metadata, nil }), ) if len(c.ExpectErr) != 0 { if err == nil { t.Fatalf("expect error, got none") } if e, a := c.ExpectErr, err.Error(); !strings.Contains(a, e) { t.Fatalf("expect error to contain %q, got %v", e, a) } return } else if err != nil { t.Fatalf("expect no error, got %v", err) } if e, a := c.ExpectLen, updatedRequest.ContentLength; e != a { t.Errorf("expect %v content-length, got %v", e, a) } if e, a := c.ExpectNilStream, updatedRequest.stream == nil; e != a { t.Errorf("expect %v nil stream, got %v", e, a) } }) } } func TestContentLengthMiddleware_HeaderSet(t *testing.T) { req := NewStackRequest().(*Request) req.Header.Set("Content-Length", "1234") var err error req, err = req.SetStream(strings.NewReader("hello")) if err != nil { t.Fatalf("expect to set stream, %v", err) } var m ComputeContentLength _, _, err = m.HandleBuild(context.Background(), middleware.BuildInput{Request: req}, nopBuildHandler, ) if err != nil { t.Fatalf("expect middleware to run, %v", err) } if e, a := "1234", req.Header.Get("Content-Length"); e != a { t.Errorf("expect Content-Length not to change, got %v", a) } } var nopBuildHandler = middleware.BuildHandlerFunc(func(ctx context.Context, input middleware.BuildInput) ( out middleware.BuildOutput, metadata middleware.Metadata, err error) { return out, metadata, nil }) type basicReader struct { buf []byte } func (r *basicReader) Read(p []byte) (int, error) { n := copy(p, r.buf) r.buf = r.buf[n:] return n, nil } type errorSecondSeekableReader struct { err error count int } func (r *errorSecondSeekableReader) Read(p []byte) (int, error) { return 0, io.EOF } func (r *errorSecondSeekableReader) Seek(offset int64, whence int) (int64, error) { r.count++ if r.count == 2 { return 0, r.err } return 0, nil } func TestValidateContentLengthHeader(t *testing.T) { cases := map[string]struct { contentLength int64 expectError string }{ "success": { contentLength: 10, }, "length set to 0": { contentLength: 0, }, "content-length unset": { contentLength: -1, expectError: "content length for payload is required and must be at least 0", }, } for name, c := range cases { t.Run(name, func(t *testing.T) { var err error req := NewStackRequest().(*Request) req.ContentLength = c.contentLength var m validateContentLength _, _, err = m.HandleBuild(context.Background(), middleware.BuildInput{Request: req}, nopBuildHandler, ) if len(c.expectError) != 0 { if err == nil { t.Fatalf("expect error, got none") } if e, a := c.expectError, err.Error(); !strings.Contains(a, e) { t.Fatalf("expect error to contain %q, got %v", e, a) } } else if err != nil { t.Fatalf("expect no error, got %v", err) } }) } } smithy-go-1.20.3/transport/http/middleware_header_comment.go000066400000000000000000000044021463735525100242410ustar00rootroot00000000000000package http import ( "context" "fmt" "net/http" "github.com/aws/smithy-go/middleware" ) // WithHeaderComment instruments a middleware stack to append an HTTP field // comment to the given header as specified in RFC 9110 // (https://www.rfc-editor.org/rfc/rfc9110#name-comments). // // The header is case-insensitive. If the provided header exists when the // middleware runs, the content will be inserted as-is enclosed in parentheses. // // Note that per the HTTP specification, comments are only allowed in fields // containing "comment" as part of their field value definition, but this API // will NOT verify whether the provided header is one of them. // // WithHeaderComment MAY be applied more than once to a middleware stack and/or // more than once per header. func WithHeaderComment(header, content string) func(*middleware.Stack) error { return func(s *middleware.Stack) error { m, err := getOrAddHeaderComment(s) if err != nil { return fmt.Errorf("get or add header comment: %v", err) } m.values.Add(header, content) return nil } } type headerCommentMiddleware struct { values http.Header // hijack case-insensitive access APIs } func (*headerCommentMiddleware) ID() string { return "headerComment" } func (m *headerCommentMiddleware) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) ( out middleware.BuildOutput, metadata middleware.Metadata, err error, ) { r, ok := in.Request.(*Request) if !ok { return out, metadata, fmt.Errorf("unknown transport type %T", in.Request) } for h, contents := range m.values { for _, c := range contents { if existing := r.Header.Get(h); existing != "" { r.Header.Set(h, fmt.Sprintf("%s (%s)", existing, c)) } } } return next.HandleBuild(ctx, in) } func getOrAddHeaderComment(s *middleware.Stack) (*headerCommentMiddleware, error) { id := (*headerCommentMiddleware)(nil).ID() m, ok := s.Build.Get(id) if !ok { m := &headerCommentMiddleware{values: http.Header{}} if err := s.Build.Add(m, middleware.After); err != nil { return nil, fmt.Errorf("add build: %v", err) } return m, nil } hc, ok := m.(*headerCommentMiddleware) if !ok { return nil, fmt.Errorf("existing middleware w/ id %s is not *headerCommentMiddleware", id) } return hc, nil } smithy-go-1.20.3/transport/http/middleware_header_comment_test.go000066400000000000000000000051161463735525100253030ustar00rootroot00000000000000package http import ( "context" "net/http" "testing" "github.com/aws/smithy-go/middleware" ) func TestWithHeaderComment_CaseInsensitive(t *testing.T) { stack, err := newTestStack( WithHeaderComment("foo", "bar"), ) if err != nil { t.Errorf("expected no error on new stack, got %v", err) } r := injectBuildRequest(stack) r.Header.Set("Foo", "baz") if err := handle(stack); err != nil { t.Errorf("expected no error on handle, got %v", err) } expectHeader(t, r.Header, "Foo", "baz (bar)") } func TestWithHeaderComment_Noop(t *testing.T) { stack, err := newTestStack( WithHeaderComment("foo", "bar"), ) if err != nil { t.Errorf("expected no error on new stack, got %v", err) } r := injectBuildRequest(stack) if err := handle(stack); err != nil { t.Errorf("expected no error on handle, got %v", err) } expectHeader(t, r.Header, "Foo", "") } func TestWithHeaderComment_MultiCaseInsensitive(t *testing.T) { stack, err := newTestStack( WithHeaderComment("foo", "c1"), WithHeaderComment("Foo", "c2"), WithHeaderComment("baz", "c3"), WithHeaderComment("Baz", "c4"), ) if err != nil { t.Errorf("expected no error on new stack, got %v", err) } r := injectBuildRequest(stack) r.Header.Set("Foo", "1") r.Header.Set("Baz", "2") if err := handle(stack); err != nil { t.Errorf("expected no error on handle, got %v", err) } expectHeader(t, r.Header, "Foo", "1 (c1) (c2)") expectHeader(t, r.Header, "Baz", "2 (c3) (c4)") } func newTestStack(fns ...func(*middleware.Stack) error) (*middleware.Stack, error) { s := middleware.NewStack("", NewStackRequest) for _, fn := range fns { if err := fn(s); err != nil { return nil, err } } return s, nil } func handle(stack *middleware.Stack) error { _, _, err := middleware.DecorateHandler( middleware.HandlerFunc( func(ctx context.Context, input interface{}) ( interface{}, middleware.Metadata, error, ) { return nil, middleware.Metadata{}, nil }, ), stack, ).Handle(context.Background(), nil) return err } func injectBuildRequest(s *middleware.Stack) *Request { r := NewStackRequest() s.Build.Add( middleware.BuildMiddlewareFunc( "injectBuildRequest", func(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) ( middleware.BuildOutput, middleware.Metadata, error, ) { return next.HandleBuild(ctx, middleware.BuildInput{Request: r}) }, ), middleware.Before, ) return r.(*Request) } func expectHeader(t *testing.T, header http.Header, h, ev string) { if av := header.Get(h); ev != av { t.Errorf("expected header '%s: %s', got '%s'", h, ev, av) } } smithy-go-1.20.3/transport/http/middleware_headers.go000066400000000000000000000122131463735525100227010ustar00rootroot00000000000000package http import ( "context" "fmt" "github.com/aws/smithy-go/middleware" ) type isContentTypeAutoSet struct{} // SetIsContentTypeDefaultValue returns a Context specifying if the request's // content-type header was set to a default value. func SetIsContentTypeDefaultValue(ctx context.Context, isDefault bool) context.Context { return context.WithValue(ctx, isContentTypeAutoSet{}, isDefault) } // GetIsContentTypeDefaultValue returns if the content-type HTTP header on the // request is a default value that was auto assigned by an operation // serializer. Allows middleware post serialization to know if the content-type // was auto set to a default value or not. // // Also returns false if the Context value was never updated to include if // content-type was set to a default value. func GetIsContentTypeDefaultValue(ctx context.Context) bool { v, _ := ctx.Value(isContentTypeAutoSet{}).(bool) return v } // AddNoPayloadDefaultContentTypeRemover Adds the DefaultContentTypeRemover // middleware to the stack after the operation serializer. This middleware will // remove the content-type header from the request if it was set as a default // value, and no request payload is present. // // Returns error if unable to add the middleware. func AddNoPayloadDefaultContentTypeRemover(stack *middleware.Stack) (err error) { err = stack.Serialize.Insert(removeDefaultContentType{}, "OperationSerializer", middleware.After) if err != nil { return fmt.Errorf("failed to add %s serialize middleware, %w", removeDefaultContentType{}.ID(), err) } return nil } // RemoveNoPayloadDefaultContentTypeRemover removes the // DefaultContentTypeRemover middleware from the stack. Returns an error if // unable to remove the middleware. func RemoveNoPayloadDefaultContentTypeRemover(stack *middleware.Stack) (err error) { _, err = stack.Serialize.Remove(removeDefaultContentType{}.ID()) if err != nil { return fmt.Errorf("failed to remove %s serialize middleware, %w", removeDefaultContentType{}.ID(), err) } return nil } // removeDefaultContentType provides after serialization middleware that will // remove the content-type header from an HTTP request if the header was set as // a default value by the operation serializer, and there is no request payload. type removeDefaultContentType struct{} // ID returns the middleware ID func (removeDefaultContentType) ID() string { return "RemoveDefaultContentType" } // HandleSerialize implements the serialization middleware. func (removeDefaultContentType) HandleSerialize( ctx context.Context, input middleware.SerializeInput, next middleware.SerializeHandler, ) ( out middleware.SerializeOutput, meta middleware.Metadata, err error, ) { req, ok := input.Request.(*Request) if !ok { return out, meta, fmt.Errorf( "unexpected request type %T for removeDefaultContentType middleware", input.Request) } if GetIsContentTypeDefaultValue(ctx) && req.GetStream() == nil { req.Header.Del("Content-Type") input.Request = req } return next.HandleSerialize(ctx, input) } type headerValue struct { header string value string append bool } type headerValueHelper struct { headerValues []headerValue } func (h *headerValueHelper) addHeaderValue(value headerValue) { h.headerValues = append(h.headerValues, value) } func (h *headerValueHelper) ID() string { return "HTTPHeaderHelper" } func (h *headerValueHelper) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (out middleware.BuildOutput, metadata middleware.Metadata, err error) { req, ok := in.Request.(*Request) if !ok { return out, metadata, fmt.Errorf("unknown transport type %T", in.Request) } for _, value := range h.headerValues { if value.append { req.Header.Add(value.header, value.value) } else { req.Header.Set(value.header, value.value) } } return next.HandleBuild(ctx, in) } func getOrAddHeaderValueHelper(stack *middleware.Stack) (*headerValueHelper, error) { id := (*headerValueHelper)(nil).ID() m, ok := stack.Build.Get(id) if !ok { m = &headerValueHelper{} err := stack.Build.Add(m, middleware.After) if err != nil { return nil, err } } requestUserAgent, ok := m.(*headerValueHelper) if !ok { return nil, fmt.Errorf("%T for %s middleware did not match expected type", m, id) } return requestUserAgent, nil } // AddHeaderValue returns a stack mutator that adds the header value pair to header. // Appends to any existing values if present. func AddHeaderValue(header string, value string) func(stack *middleware.Stack) error { return func(stack *middleware.Stack) error { helper, err := getOrAddHeaderValueHelper(stack) if err != nil { return err } helper.addHeaderValue(headerValue{header: header, value: value, append: true}) return nil } } // SetHeaderValue returns a stack mutator that adds the header value pair to header. // Replaces any existing values if present. func SetHeaderValue(header string, value string) func(stack *middleware.Stack) error { return func(stack *middleware.Stack) error { helper, err := getOrAddHeaderValueHelper(stack) if err != nil { return err } helper.addHeaderValue(headerValue{header: header, value: value, append: false}) return nil } } smithy-go-1.20.3/transport/http/middleware_headers_test.go000066400000000000000000000041051463735525100237410ustar00rootroot00000000000000package http_test import ( "context" "net/http" "reflect" "testing" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" ) func TestAddHeaderValue(t *testing.T) { stack := middleware.NewStack("stack", smithyhttp.NewStackRequest) err := smithyhttp.AddHeaderValue("foo", "fooValue")(stack) if err != nil { t.Fatalf("expect no error, got %v", err) } err = smithyhttp.AddHeaderValue("bar", "firstValue")(stack) if err != nil { t.Fatalf("expect no error, got %v", err) } err = smithyhttp.AddHeaderValue("bar", "secondValue")(stack) if err != nil { t.Fatalf("expect no error, got %v", err) } handler := middleware.DecorateHandler(middleware.HandlerFunc(func(ctx context.Context, input interface{}) (output interface{}, metadata middleware.Metadata, err error) { req := input.(*smithyhttp.Request) expect := http.Header{ "Foo": []string{"fooValue"}, "Bar": []string{"firstValue", "secondValue"}, } if !reflect.DeepEqual(expect, req.Header) { t.Errorf("%v != %v", expect, req.Header) } return output, metadata, err }), stack) _, _, err = handler.Handle(context.Background(), nil) if err != nil { t.Fatalf("expect no error, got %v", err) } } func TestSetHeaderValue(t *testing.T) { stack := middleware.NewStack("stack", smithyhttp.NewStackRequest) err := smithyhttp.SetHeaderValue("foo", "firstValue")(stack) if err != nil { t.Fatalf("expect no error, got %v", err) } err = smithyhttp.SetHeaderValue("foo", "secondValue")(stack) if err != nil { t.Fatalf("expect no error, got %v", err) } handler := middleware.DecorateHandler(middleware.HandlerFunc(func(ctx context.Context, input interface{}) (output interface{}, metadata middleware.Metadata, err error) { req := input.(*smithyhttp.Request) expect := http.Header{ "Foo": []string{"secondValue"}, } if !reflect.DeepEqual(expect, req.Header) { t.Errorf("%v != %v", expect, req.Header) } return output, metadata, err }), stack) _, _, err = handler.Handle(context.Background(), nil) if err != nil { t.Fatalf("expect no error, got %v", err) } } smithy-go-1.20.3/transport/http/middleware_http_logging.go000066400000000000000000000040451463735525100237570ustar00rootroot00000000000000package http import ( "context" "fmt" "net/http/httputil" "github.com/aws/smithy-go/logging" "github.com/aws/smithy-go/middleware" ) // RequestResponseLogger is a deserialize middleware that will log the request and response HTTP messages and optionally // their respective bodies. Will not perform any logging if none of the options are set. type RequestResponseLogger struct { LogRequest bool LogRequestWithBody bool LogResponse bool LogResponseWithBody bool } // ID is the middleware identifier. func (r *RequestResponseLogger) ID() string { return "RequestResponseLogger" } // HandleDeserialize will log the request and response HTTP messages if configured accordingly. func (r *RequestResponseLogger) HandleDeserialize( ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler, ) ( out middleware.DeserializeOutput, metadata middleware.Metadata, err error, ) { logger := middleware.GetLogger(ctx) if r.LogRequest || r.LogRequestWithBody { smithyRequest, ok := in.Request.(*Request) if !ok { return out, metadata, fmt.Errorf("unknown transport type %T", in) } rc := smithyRequest.Build(ctx) reqBytes, err := httputil.DumpRequestOut(rc, r.LogRequestWithBody) if err != nil { return out, metadata, err } logger.Logf(logging.Debug, "Request\n%v", string(reqBytes)) if r.LogRequestWithBody { smithyRequest, err = smithyRequest.SetStream(rc.Body) if err != nil { return out, metadata, err } in.Request = smithyRequest } } out, metadata, err = next.HandleDeserialize(ctx, in) if (err == nil) && (r.LogResponse || r.LogResponseWithBody) { smithyResponse, ok := out.RawResponse.(*Response) if !ok { return out, metadata, fmt.Errorf("unknown transport type %T", out.RawResponse) } respBytes, err := httputil.DumpResponse(smithyResponse.Response, r.LogResponseWithBody) if err != nil { return out, metadata, fmt.Errorf("failed to dump response %w", err) } logger.Logf(logging.Debug, "Response\n%v", string(respBytes)) } return out, metadata, err } smithy-go-1.20.3/transport/http/middleware_http_logging_test.go000066400000000000000000000077301463735525100250220ustar00rootroot00000000000000package http_test import ( "bytes" "context" "fmt" "io" "io/ioutil" "net/http" "net/url" "testing" "github.com/aws/smithy-go/logging" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" ) type mockLogger struct { bytes.Buffer } func (m *mockLogger) Logf(_ logging.Classification, format string, v ...interface{}) { m.Buffer.WriteString(fmt.Sprintf(format, v...)) m.Buffer.WriteRune('\n') } func TestRequestResponseLogger(t *testing.T) { cases := map[string]struct { Middleware smithyhttp.RequestResponseLogger Input *smithyhttp.Request InputBody io.ReadCloser Output *smithyhttp.Response ExpectedLog string }{ "no logging": {}, "request": { Middleware: smithyhttp.RequestResponseLogger{ LogRequest: true, }, Input: &smithyhttp.Request{ Request: &http.Request{ URL: &url.URL{ Scheme: "https", Path: "/foo", Host: "example.amazonaws.com", }, Header: map[string][]string{ "Foo": {"bar"}, }, }, }, InputBody: ioutil.NopCloser(bytes.NewReader([]byte(`this is the body`))), ExpectedLog: "Request\n" + "GET /foo HTTP/1.1\r\n" + "Host: example.amazonaws.com\r\n" + "User-Agent: Go-http-client/1.1\r\n" + "Foo: bar\r\n" + "Accept-Encoding: gzip\r\n" + "\r\n\n", }, "request with body": { Middleware: smithyhttp.RequestResponseLogger{ LogRequestWithBody: true, }, Input: &smithyhttp.Request{ Request: &http.Request{ URL: &url.URL{ Scheme: "https", Path: "/foo", Host: "example.amazonaws.com", }, Header: map[string][]string{ "Foo": {"bar"}, }, ContentLength: 16, }, }, InputBody: ioutil.NopCloser(bytes.NewReader([]byte(`this is the body`))), ExpectedLog: "Request\n" + "GET /foo HTTP/1.1\r\n" + "Host: example.amazonaws.com\r\n" + "User-Agent: Go-http-client/1.1\r\n" + "Content-Length: 16\r\n" + "Foo: bar\r\n" + "Accept-Encoding: gzip\r\n" + "\r\n" + "this is the body\n", }, "response": { Middleware: smithyhttp.RequestResponseLogger{ LogResponse: true, }, Output: &smithyhttp.Response{ Response: &http.Response{ StatusCode: 200, Proto: "HTTP/1.1", ContentLength: 16, Header: map[string][]string{ "Foo": {"Bar"}, }, Body: ioutil.NopCloser(bytes.NewReader([]byte(`this is the body`))), }, }, ExpectedLog: "Response\n" + "HTTP/0.0 200 OK\r\n" + "Content-Length: 16\r\n" + "Foo: Bar\r\n" + "\r\n\n", }, "response with body": { Middleware: smithyhttp.RequestResponseLogger{ LogResponseWithBody: true, }, Output: &smithyhttp.Response{ Response: &http.Response{ StatusCode: 200, Proto: "HTTP/1.1", ContentLength: 16, Header: map[string][]string{ "Foo": {"Bar"}, }, Body: ioutil.NopCloser(bytes.NewReader([]byte(`this is the body`))), }, }, ExpectedLog: "Response\n" + "HTTP/0.0 200 OK\r\n" + "Content-Length: 16\r\n" + "Foo: Bar\r\n" + "\r\n" + "this is the body\n", }, } for name, tt := range cases { t.Run(name, func(t *testing.T) { logger := mockLogger{} ctx := middleware.SetLogger(context.Background(), &logger) var err error if tt.InputBody != nil { tt.Input, err = tt.Input.SetStream(tt.InputBody) if err != nil { t.Errorf("expect no error, got %v", err) } } _, _, err = tt.Middleware.HandleDeserialize(ctx, middleware.DeserializeInput{Request: tt.Input}, middleware.DeserializeHandlerFunc(func(ctx context.Context, input middleware.DeserializeInput) ( middleware.DeserializeOutput, middleware.Metadata, error, ) { return middleware.DeserializeOutput{RawResponse: tt.Output}, middleware.Metadata{}, nil })) if err != nil { t.Fatal("expect error, got nil") } actual := string(logger.Bytes()) if tt.ExpectedLog != actual { t.Errorf("%v != %v", tt.ExpectedLog, actual) } }) } } smithy-go-1.20.3/transport/http/middleware_metadata.go000066400000000000000000000031051463735525100230460ustar00rootroot00000000000000package http import ( "context" "github.com/aws/smithy-go/middleware" ) type ( hostnameImmutableKey struct{} hostPrefixDisableKey struct{} ) // GetHostnameImmutable retrieves whether the endpoint hostname should be considered // immutable or not. // // Scoped to stack values. Use middleware#ClearStackValues to clear all stack // values. func GetHostnameImmutable(ctx context.Context) (v bool) { v, _ = middleware.GetStackValue(ctx, hostnameImmutableKey{}).(bool) return v } // SetHostnameImmutable sets or modifies whether the request's endpoint hostname // should be considered immutable or not. // // Scoped to stack values. Use middleware#ClearStackValues to clear all stack // values. func SetHostnameImmutable(ctx context.Context, value bool) context.Context { return middleware.WithStackValue(ctx, hostnameImmutableKey{}, value) } // IsEndpointHostPrefixDisabled retrieves whether the hostname prefixing is // disabled. // // Scoped to stack values. Use middleware#ClearStackValues to clear all stack // values. func IsEndpointHostPrefixDisabled(ctx context.Context) (v bool) { v, _ = middleware.GetStackValue(ctx, hostPrefixDisableKey{}).(bool) return v } // DisableEndpointHostPrefix sets or modifies whether the request's endpoint host // prefixing should be disabled. If value is true, endpoint host prefixing // will be disabled. // // Scoped to stack values. Use middleware#ClearStackValues to clear all stack // values. func DisableEndpointHostPrefix(ctx context.Context, value bool) context.Context { return middleware.WithStackValue(ctx, hostPrefixDisableKey{}, value) } smithy-go-1.20.3/transport/http/middleware_min_proto.go000066400000000000000000000045641463735525100233060ustar00rootroot00000000000000package http import ( "context" "fmt" "github.com/aws/smithy-go/middleware" "strings" ) // MinimumProtocolError is an error type indicating that the established connection did not meet the expected minimum // HTTP protocol version. type MinimumProtocolError struct { proto string expectedProtoMajor int expectedProtoMinor int } // Error returns the error message. func (m *MinimumProtocolError) Error() string { return fmt.Sprintf("operation requires minimum HTTP protocol of HTTP/%d.%d, but was %s", m.expectedProtoMajor, m.expectedProtoMinor, m.proto) } // RequireMinimumProtocol is a deserialization middleware that asserts that the established HTTP connection // meets the minimum major ad minor version. type RequireMinimumProtocol struct { ProtoMajor int ProtoMinor int } // AddRequireMinimumProtocol adds the RequireMinimumProtocol middleware to the stack using the provided minimum // protocol major and minor version. func AddRequireMinimumProtocol(stack *middleware.Stack, major, minor int) error { return stack.Deserialize.Insert(&RequireMinimumProtocol{ ProtoMajor: major, ProtoMinor: minor, }, "OperationDeserializer", middleware.Before) } // ID returns the middleware identifier string. func (r *RequireMinimumProtocol) ID() string { return "RequireMinimumProtocol" } // HandleDeserialize asserts that the established connection is a HTTP connection with the minimum major and minor // protocol version. func (r *RequireMinimumProtocol) HandleDeserialize( ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler, ) ( out middleware.DeserializeOutput, metadata middleware.Metadata, err error, ) { out, metadata, err = next.HandleDeserialize(ctx, in) if err != nil { return out, metadata, err } response, ok := out.RawResponse.(*Response) if !ok { return out, metadata, fmt.Errorf("unknown transport type: %T", out.RawResponse) } if !strings.HasPrefix(response.Proto, "HTTP") { return out, metadata, &MinimumProtocolError{ proto: response.Proto, expectedProtoMajor: r.ProtoMajor, expectedProtoMinor: r.ProtoMinor, } } if response.ProtoMajor < r.ProtoMajor || response.ProtoMinor < r.ProtoMinor { return out, metadata, &MinimumProtocolError{ proto: response.Proto, expectedProtoMajor: r.ProtoMajor, expectedProtoMinor: r.ProtoMinor, } } return out, metadata, err } smithy-go-1.20.3/transport/http/properties.go000066400000000000000000000050621463735525100212710ustar00rootroot00000000000000package http import smithy "github.com/aws/smithy-go" type ( sigV4SigningNameKey struct{} sigV4SigningRegionKey struct{} sigV4ASigningNameKey struct{} sigV4ASigningRegionsKey struct{} isUnsignedPayloadKey struct{} disableDoubleEncodingKey struct{} ) // GetSigV4SigningName gets the signing name from Properties. func GetSigV4SigningName(p *smithy.Properties) (string, bool) { v, ok := p.Get(sigV4SigningNameKey{}).(string) return v, ok } // SetSigV4SigningName sets the signing name on Properties. func SetSigV4SigningName(p *smithy.Properties, name string) { p.Set(sigV4SigningNameKey{}, name) } // GetSigV4SigningRegion gets the signing region from Properties. func GetSigV4SigningRegion(p *smithy.Properties) (string, bool) { v, ok := p.Get(sigV4SigningRegionKey{}).(string) return v, ok } // SetSigV4SigningRegion sets the signing region on Properties. func SetSigV4SigningRegion(p *smithy.Properties, region string) { p.Set(sigV4SigningRegionKey{}, region) } // GetSigV4ASigningName gets the v4a signing name from Properties. func GetSigV4ASigningName(p *smithy.Properties) (string, bool) { v, ok := p.Get(sigV4ASigningNameKey{}).(string) return v, ok } // SetSigV4ASigningName sets the signing name on Properties. func SetSigV4ASigningName(p *smithy.Properties, name string) { p.Set(sigV4ASigningNameKey{}, name) } // GetSigV4ASigningRegion gets the v4a signing region set from Properties. func GetSigV4ASigningRegions(p *smithy.Properties) ([]string, bool) { v, ok := p.Get(sigV4ASigningRegionsKey{}).([]string) return v, ok } // SetSigV4ASigningRegions sets the v4a signing region set on Properties. func SetSigV4ASigningRegions(p *smithy.Properties, regions []string) { p.Set(sigV4ASigningRegionsKey{}, regions) } // GetIsUnsignedPayload gets whether the payload is unsigned from Properties. func GetIsUnsignedPayload(p *smithy.Properties) (bool, bool) { v, ok := p.Get(isUnsignedPayloadKey{}).(bool) return v, ok } // SetIsUnsignedPayload sets whether the payload is unsigned on Properties. func SetIsUnsignedPayload(p *smithy.Properties, isUnsignedPayload bool) { p.Set(isUnsignedPayloadKey{}, isUnsignedPayload) } // GetDisableDoubleEncoding gets whether the payload is unsigned from Properties. func GetDisableDoubleEncoding(p *smithy.Properties) (bool, bool) { v, ok := p.Get(disableDoubleEncodingKey{}).(bool) return v, ok } // SetDisableDoubleEncoding sets whether the payload is unsigned on Properties. func SetDisableDoubleEncoding(p *smithy.Properties, disableDoubleEncoding bool) { p.Set(disableDoubleEncodingKey{}, disableDoubleEncoding) } smithy-go-1.20.3/transport/http/properties_test.go000066400000000000000000000034121463735525100223250ustar00rootroot00000000000000package http import ( "testing" "reflect" smithy "github.com/aws/smithy-go" ) func TestSigV4SigningName(t *testing.T) { expected := "foo" var m smithy.Properties SetSigV4SigningName(&m, expected) actual, _ := GetSigV4SigningName(&m) if expected != actual { t.Errorf("Expect SigV4SigningName to be equivalent %s != %s", expected, actual) } } func TestSigV4SigningRegion(t *testing.T) { expected := "foo" var m smithy.Properties SetSigV4SigningRegion(&m, expected) actual, _ := GetSigV4SigningRegion(&m) if expected != actual { t.Errorf("Expect SigV4SigningRegion to be equivalent %s != %s", expected, actual) } } func TestSigV4ASigningName(t *testing.T) { expected := "foo" var m smithy.Properties SetSigV4ASigningName(&m, expected) actual, _ := GetSigV4ASigningName(&m) if expected != actual { t.Errorf("Expect SigV4ASigningName to be equivalent %s != %s", expected, actual) } } func TestSigV4SigningRegions(t *testing.T) { expected := []string{"foo", "bar"} var m smithy.Properties SetSigV4ASigningRegions(&m, expected) actual, _ := GetSigV4ASigningRegions(&m) if !reflect.DeepEqual(expected, actual) { t.Errorf("Expect SigV4ASigningRegions to be equivalent %v != %v", expected, actual) } } func TestUnsignedPayload(t *testing.T) { expected := true var m smithy.Properties SetIsUnsignedPayload(&m, expected) actual, _ := GetIsUnsignedPayload(&m) if expected != actual { t.Errorf("Expect IsUnsignedPayload to be equivalent %v != %v", expected, actual) } } func TestDisableDoubleEncoding(t *testing.T) { expected := true var m smithy.Properties SetDisableDoubleEncoding(&m, expected) actual, _ := GetDisableDoubleEncoding(&m) if expected != actual { t.Errorf("Expect DisableDoubleEncoding to be equivalent %v != %v", expected, actual) } }smithy-go-1.20.3/transport/http/request.go000066400000000000000000000123501463735525100205630ustar00rootroot00000000000000package http import ( "context" "fmt" "io" "io/ioutil" "net/http" "net/url" "strings" iointernal "github.com/aws/smithy-go/transport/http/internal/io" ) // Request provides the HTTP specific request structure for HTTP specific // middleware steps to use to serialize input, and send an operation's request. type Request struct { *http.Request stream io.Reader isStreamSeekable bool streamStartPos int64 } // NewStackRequest returns an initialized request ready to be populated with the // HTTP request details. Returns empty interface so the function can be used as // a parameter to the Smithy middleware Stack constructor. func NewStackRequest() interface{} { return &Request{ Request: &http.Request{ URL: &url.URL{}, Header: http.Header{}, ContentLength: -1, // default to unknown length }, } } // IsHTTPS returns if the request is HTTPS. Returns false if no endpoint URL is set. func (r *Request) IsHTTPS() bool { if r.URL == nil { return false } return strings.EqualFold(r.URL.Scheme, "https") } // Clone returns a deep copy of the Request for the new context. A reference to // the Stream is copied, but the underlying stream is not copied. func (r *Request) Clone() *Request { rc := *r rc.Request = rc.Request.Clone(context.TODO()) return &rc } // StreamLength returns the number of bytes of the serialized stream attached // to the request and ok set. If the length cannot be determined, an error will // be returned. func (r *Request) StreamLength() (size int64, ok bool, err error) { return streamLength(r.stream, r.isStreamSeekable, r.streamStartPos) } func streamLength(stream io.Reader, seekable bool, startPos int64) (size int64, ok bool, err error) { if stream == nil { return 0, true, nil } if l, ok := stream.(interface{ Len() int }); ok { return int64(l.Len()), true, nil } if !seekable { return 0, false, nil } s := stream.(io.Seeker) endOffset, err := s.Seek(0, io.SeekEnd) if err != nil { return 0, false, err } // The reason to seek to streamStartPos instead of 0 is to ensure that the // SDK only sends the stream from the starting position the user's // application provided it to the SDK at. For example application opens a // file, and wants to skip the first N bytes uploading the rest. The // application would move the file's offset N bytes, then hand it off to // the SDK to send the remaining. The SDK should respect that initial offset. _, err = s.Seek(startPos, io.SeekStart) if err != nil { return 0, false, err } return endOffset - startPos, true, nil } // RewindStream will rewind the io.Reader to the relative start position if it // is an io.Seeker. func (r *Request) RewindStream() error { // If there is no stream there is nothing to rewind. if r.stream == nil { return nil } if !r.isStreamSeekable { return fmt.Errorf("request stream is not seekable") } _, err := r.stream.(io.Seeker).Seek(r.streamStartPos, io.SeekStart) return err } // GetStream returns the request stream io.Reader if a stream is set. If no // stream is present nil will be returned. func (r *Request) GetStream() io.Reader { return r.stream } // IsStreamSeekable returns whether the stream is seekable. func (r *Request) IsStreamSeekable() bool { return r.isStreamSeekable } // SetStream returns a clone of the request with the stream set to the provided // reader. May return an error if the provided reader is seekable but returns // an error. func (r *Request) SetStream(reader io.Reader) (rc *Request, err error) { rc = r.Clone() if reader == http.NoBody { reader = nil } var isStreamSeekable bool var streamStartPos int64 switch v := reader.(type) { case io.Seeker: n, err := v.Seek(0, io.SeekCurrent) if err != nil { return r, err } isStreamSeekable = true streamStartPos = n default: // If the stream length can be determined, and is determined to be empty, // use a nil stream to prevent confusion between empty vs not-empty // streams. length, ok, err := streamLength(reader, false, 0) if err != nil { return nil, err } else if ok && length == 0 { reader = nil } } rc.stream = reader rc.isStreamSeekable = isStreamSeekable rc.streamStartPos = streamStartPos return rc, err } // Build returns a build standard HTTP request value from the Smithy request. // The request's stream is wrapped in a safe container that allows it to be // reused for subsequent attempts. func (r *Request) Build(ctx context.Context) *http.Request { req := r.Request.Clone(ctx) if r.stream == nil && req.ContentLength == -1 { req.ContentLength = 0 } switch stream := r.stream.(type) { case *io.PipeReader: req.Body = ioutil.NopCloser(stream) req.ContentLength = -1 default: // HTTP Client Request must only have a non-nil body if the // ContentLength is explicitly unknown (-1) or non-zero. The HTTP // Client will interpret a non-nil body and ContentLength 0 as // "unknown". This is unwanted behavior. if req.ContentLength != 0 && r.stream != nil { req.Body = iointernal.NewSafeReadCloser(ioutil.NopCloser(stream)) } } return req } // RequestCloner is a function that can take an input request type and clone the request // for use in a subsequent retry attempt. func RequestCloner(v interface{}) interface{} { return v.(*Request).Clone() } smithy-go-1.20.3/transport/http/request_test.go000066400000000000000000000121741463735525100216260ustar00rootroot00000000000000package http import ( "bytes" "context" "io" "io/ioutil" "net/http" "os" "strconv" "strings" "testing" ) func TestRequestRewindable(t *testing.T) { cases := map[string]struct { Stream io.Reader ExpectErr string }{ "rewindable": { Stream: bytes.NewReader([]byte{}), }, "empty not rewindable": { Stream: bytes.NewBuffer([]byte{}), // ExpectErr: "stream is not seekable", }, "not empty not rewindable": { Stream: bytes.NewBuffer([]byte("abc123")), ExpectErr: "stream is not seekable", }, "nil stream": {}, } for name, c := range cases { t.Run(name, func(t *testing.T) { req := NewStackRequest().(*Request) req, err := req.SetStream(c.Stream) if err != nil { t.Fatalf("expect no error setting stream, %v", err) } err = req.RewindStream() if len(c.ExpectErr) != 0 { if err == nil { t.Fatalf("expect error, got none") } if e, a := c.ExpectErr, err.Error(); !strings.Contains(a, e) { t.Fatalf("expect error to contain %v, got %v", e, a) } return } if err != nil { t.Fatalf("expect no error, got %v", err) } }) } } func TestRequestBuild_contentLength(t *testing.T) { cases := []struct { Request *Request Expected int64 }{ { Request: &Request{ Request: &http.Request{ ContentLength: 100, }, }, Expected: 100, }, { Request: &Request{ Request: &http.Request{ ContentLength: -1, }, }, Expected: 0, }, { Request: &Request{ Request: &http.Request{ ContentLength: 100, }, stream: bytes.NewReader(make([]byte, 100)), }, Expected: 100, }, { Request: &Request{ Request: &http.Request{ ContentLength: 100, }, stream: http.NoBody, }, Expected: 100, }, } for i, tt := range cases { t.Run(strconv.Itoa(i), func(t *testing.T) { build := tt.Request.Build(context.Background()) if build.ContentLength != tt.Expected { t.Errorf("expect %v, got %v", tt.Expected, build.ContentLength) } }) } } func TestRequestSetStream(t *testing.T) { cases := map[string]struct { reader io.Reader expectSeekable bool expectStreamStartPos int64 expectContentLength int64 expectNilStream bool expectNilBody bool expectReqContentLength int64 }{ "nil stream": { expectNilStream: true, expectNilBody: true, }, "empty unseekable stream": { reader: bytes.NewBuffer([]byte{}), expectNilStream: true, expectNilBody: true, }, "empty seekable stream": { reader: bytes.NewReader([]byte{}), expectContentLength: 0, expectSeekable: true, expectNilStream: false, expectNilBody: true, }, "unseekable no len stream": { reader: ioutil.NopCloser(bytes.NewBuffer([]byte("abc123"))), expectContentLength: -1, expectNilStream: false, expectNilBody: false, expectReqContentLength: -1, }, "unseekable stream": { reader: bytes.NewBuffer([]byte("abc123")), expectContentLength: 6, expectNilStream: false, expectNilBody: false, expectReqContentLength: 6, }, "seekable stream": { reader: bytes.NewReader([]byte("abc123")), expectContentLength: 6, expectNilStream: false, expectSeekable: true, expectNilBody: false, expectReqContentLength: 6, }, "offset seekable stream": { reader: func() io.Reader { r := bytes.NewReader([]byte("abc123")) _, _ = r.Seek(1, os.SEEK_SET) return r }(), expectStreamStartPos: 1, expectContentLength: 5, expectSeekable: true, expectNilStream: false, expectNilBody: false, expectReqContentLength: 5, }, "NoBody stream": { reader: http.NoBody, expectNilStream: true, expectNilBody: true, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { var err error req := NewStackRequest().(*Request) req, err = req.SetStream(c.reader) if err != nil { t.Fatalf("expect not error, got %v", err) } if e, a := c.expectSeekable, req.IsStreamSeekable(); e != a { t.Errorf("expect %v seekable, got %v", e, a) } if e, a := c.expectStreamStartPos, req.streamStartPos; e != a { t.Errorf("expect %v seek start position, got %v", e, a) } if e, a := c.expectNilStream, req.stream == nil; e != a { t.Errorf("expect %v nil stream, got %v", e, a) } if l, ok, err := req.StreamLength(); err != nil { t.Fatalf("expect no stream length error, got %v", err) } else if ok { req.ContentLength = l } if e, a := c.expectContentLength, req.ContentLength; e != a { t.Errorf("expect %v content-length, got %v", e, a) } if e, a := c.expectStreamStartPos, req.streamStartPos; e != a { t.Errorf("expect %v streamStartPos, got %v", e, a) } r := req.Build(context.Background()) if e, a := c.expectNilBody, r.Body == nil; e != a { t.Errorf("expect %v request nil body, got %v", e, a) } if e, a := c.expectContentLength, req.ContentLength; e != a { t.Errorf("expect %v request content-length, got %v", e, a) } }) } } smithy-go-1.20.3/transport/http/response.go000066400000000000000000000017211463735525100207310ustar00rootroot00000000000000package http import ( "fmt" "net/http" ) // Response provides the HTTP specific response structure for HTTP specific // middleware steps to use to deserialize the response from an operation call. type Response struct { *http.Response } // ResponseError provides the HTTP centric error type wrapping the underlying // error with the HTTP response value. type ResponseError struct { Response *Response Err error } // HTTPStatusCode returns the HTTP response status code received from the service. func (e *ResponseError) HTTPStatusCode() int { return e.Response.StatusCode } // HTTPResponse returns the HTTP response received from the service. func (e *ResponseError) HTTPResponse() *Response { return e.Response } // Unwrap returns the nested error if any, or nil. func (e *ResponseError) Unwrap() error { return e.Err } func (e *ResponseError) Error() string { return fmt.Sprintf( "http response error StatusCode: %d, %v", e.Response.StatusCode, e.Err) } smithy-go-1.20.3/transport/http/response_error_example_test.go000066400000000000000000000063211463735525100247150ustar00rootroot00000000000000package http import ( "context" "errors" "fmt" "net/http" "github.com/aws/smithy-go" "github.com/aws/smithy-go/middleware" ) func ExampleResponseError() { stack := middleware.NewStack("my cool stack", NewStackRequest) stack.Deserialize.Add(middleware.DeserializeMiddlewareFunc("wrap http response error", func(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) ( out middleware.DeserializeOutput, metadata middleware.Metadata, err error, ) { out, metadata, err = next.HandleDeserialize(ctx, in) if err == nil { // Nothing to do when there is no error. return out, metadata, err } rawResp, ok := out.RawResponse.(*Response) if !ok { // No raw response to wrap with. return out, metadata, err } // Wrap the returned error with the response error containing the // returned response. err = &ResponseError{ Response: rawResp, Err: err, } return out, metadata, err }), middleware.After, ) stack.Deserialize.Add(middleware.DeserializeMiddlewareFunc("deserialize error", func(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) ( out middleware.DeserializeOutput, metadata middleware.Metadata, err error, ) { out, metadata, err = next.HandleDeserialize(ctx, in) if err != nil { return middleware.DeserializeOutput{}, metadata, err } rawResp := out.RawResponse.(*Response) if rawResp.StatusCode == 200 { return out, metadata, nil } // Deserialize the response API error values. err = &smithy.GenericAPIError{ Code: rawResp.Header.Get("Error-Code"), Message: rawResp.Header.Get("Error-Message"), } return out, metadata, err }), middleware.After, ) // Mock example handler taking the request input and returning a response mockHandler := middleware.HandlerFunc(func(ctx context.Context, in interface{}) ( output interface{}, metadata middleware.Metadata, err error, ) { // populate the mock response with an API error and additional data. resp := &http.Response{ StatusCode: 404, Header: http.Header{ "Extra-Header": []string{"foo value"}, "Error-Code": []string{"FooException"}, "Error-Message": []string{"some message about the error"}, }, } // The handler's returned response will be available as the // DeserializeOutput.RawResponse field. return &Response{ Response: resp, }, metadata, nil }) // Use the stack to decorate the handler then invoke the decorated handler // with the inputs. handler := middleware.DecorateHandler(mockHandler, stack) _, _, err := handler.Handle(context.Background(), struct{}{}) if err == nil { fmt.Printf("expect error, got none") return } if err != nil { var apiErr smithy.APIError if errors.As(err, &apiErr) { fmt.Printf("request failed: %s, %s\n", apiErr.ErrorCode(), apiErr.ErrorMessage()) } var respErr *ResponseError if errors.As(err, &respErr) { fmt.Printf("response status: %v\n", respErr.HTTPStatusCode()) fmt.Printf("response header: %v\n", respErr.HTTPResponse().Header.Get("Extra-Header")) } } // Output: // request failed: FooException, some message about the error // response status: 404 // response header: foo value } smithy-go-1.20.3/transport/http/serialize_example_test.go000066400000000000000000000036251463735525100236410ustar00rootroot00000000000000package http import ( "context" "fmt" "net/http" "os" "strconv" "github.com/aws/smithy-go/middleware" ) func ExampleRequest_serializeMiddleware() { // Create the stack and provide the function that will create a new Request // when the SerializeStep is invoked. stack := middleware.NewStack("serialize example", NewStackRequest) type Input struct { FooName string BarCount int } // Add the serialization middleware. stack.Serialize.Add(middleware.SerializeMiddlewareFunc("example serialize", func(ctx context.Context, in middleware.SerializeInput, next middleware.SerializeHandler) ( middleware.SerializeOutput, middleware.Metadata, error, ) { req := in.Request.(*Request) input := in.Parameters.(*Input) req.Header.Set("foo-name", input.FooName) req.Header.Set("bar-count", strconv.Itoa(input.BarCount)) return next.HandleSerialize(ctx, in) }), middleware.After, ) // Mock example handler taking the request input and returning a response mockHandler := middleware.HandlerFunc(func(ctx context.Context, in interface{}) ( output interface{}, metadata middleware.Metadata, err error, ) { // Returns the standard http Request for the handler to make request // using standard http compatible client. req := in.(*Request).Build(context.Background()) fmt.Println("foo-name", req.Header.Get("foo-name")) fmt.Println("bar-count", req.Header.Get("bar-count")) return &Response{ Response: &http.Response{ StatusCode: 200, Header: http.Header{}, }, }, metadata, nil }) // Use the stack to decorate the handler then invoke the decorated handler // with the inputs. handler := middleware.DecorateHandler(mockHandler, stack) _, _, err := handler.Handle(context.Background(), &Input{FooName: "abc", BarCount: 123}) if err != nil { fmt.Fprintf(os.Stderr, "failed to call operation, %v", err) return } // Output: // foo-name abc // bar-count 123 } smithy-go-1.20.3/transport/http/time.go000066400000000000000000000005041463735525100200270ustar00rootroot00000000000000package http import ( "time" smithytime "github.com/aws/smithy-go/time" ) // ParseTime parses a time string like the HTTP Date header. This uses a more // relaxed rule set for date parsing compared to the standard library. func ParseTime(text string) (t time.Time, err error) { return smithytime.ParseHTTPDate(text) } smithy-go-1.20.3/transport/http/url.go000066400000000000000000000015651463735525100177030ustar00rootroot00000000000000package http import "strings" // JoinPath returns an absolute URL path composed of the two paths provided. // Enforces that the returned path begins with '/'. If added path is empty the // returned path suffix will match the first parameter suffix. func JoinPath(a, b string) string { if len(a) == 0 { a = "/" } else if a[0] != '/' { a = "/" + a } if len(b) != 0 && b[0] == '/' { b = b[1:] } if len(b) != 0 && len(a) > 1 && a[len(a)-1] != '/' { a = a + "/" } return a + b } // JoinRawQuery returns an absolute raw query expression. Any duplicate '&' // will be collapsed to single separator between values. func JoinRawQuery(a, b string) string { a = strings.TrimFunc(a, isAmpersand) b = strings.TrimFunc(b, isAmpersand) if len(a) == 0 { return b } if len(b) == 0 { return a } return a + "&" + b } func isAmpersand(v rune) bool { return v == '&' } smithy-go-1.20.3/transport/http/url_test.go000066400000000000000000000036101463735525100207330ustar00rootroot00000000000000package http import ( "fmt" "testing" ) func TestJoinPath(t *testing.T) { cases := []struct { A, B string Expect string }{ 0: { A: "", B: "/bar", Expect: "/bar", }, 1: { A: "/", B: "/bar", Expect: "/bar", }, 2: { A: "/foo/", B: "/bar", Expect: "/foo/bar", }, 3: { A: "foo/", B: "/bar", Expect: "/foo/bar", }, 4: { A: "/foo/", B: "bar", Expect: "/foo/bar", }, 5: { A: "foo", B: "/bar", Expect: "/foo/bar", }, 6: { A: "", B: "", Expect: "/", }, 7: { A: "foo", B: "", Expect: "/foo", }, 8: { A: "foo/", B: "", Expect: "/foo/", }, 9: { A: "foo//", B: "//bar", Expect: "/foo///bar", }, } for i, c := range cases { t.Run(fmt.Sprintf("%d:%s,%s:%s", i, c.A, c.B, c.Expect), func(t *testing.T) { actual := JoinPath(c.A, c.B) if e, a := c.Expect, actual; e != a { t.Errorf("expect %v path, got %v", e, a) } }) } } func TestJoinRawQuery(t *testing.T) { cases := []struct { A, B string Expect string }{ 0: { A: "", B: "bar", Expect: "bar", }, 1: { A: "foo", B: "bar", Expect: "foo&bar", }, 2: { A: "foo&", B: "bar", Expect: "foo&bar", }, 3: { A: "foo", B: "", Expect: "foo", }, 4: { A: "", B: "&bar", Expect: "bar", }, 5: { A: "foo&", B: "&bar", Expect: "foo&bar", }, 6: { A: "", B: "", Expect: "", }, 7: { A: "foo&baz", B: "bar", Expect: "foo&baz&bar", }, 8: { A: "foo", B: "baz&bar", Expect: "foo&baz&bar", }, 9: { A: "&foo&", B: "&baz&bar&", Expect: "foo&baz&bar", }, 10: { A: "&foo&&&", B: "&&&baz&&&bar&", Expect: "foo&baz&&&bar", }, } for i, c := range cases { t.Run(fmt.Sprintf("%d:%s,%s:%s", i, c.A, c.B, c.Expect), func(t *testing.T) { actual := JoinRawQuery(c.A, c.B) if e, a := c.Expect, actual; e != a { t.Errorf("expect %v query, got %v", e, a) } }) } } smithy-go-1.20.3/transport/http/user_agent.go000066400000000000000000000015651463735525100212350ustar00rootroot00000000000000package http import ( "strings" ) // UserAgentBuilder is a builder for a HTTP User-Agent string. type UserAgentBuilder struct { sb strings.Builder } // NewUserAgentBuilder returns a new UserAgentBuilder. func NewUserAgentBuilder() *UserAgentBuilder { return &UserAgentBuilder{sb: strings.Builder{}} } // AddKey adds the named component/product to the agent string func (u *UserAgentBuilder) AddKey(key string) { u.appendTo(key) } // AddKeyValue adds the named key to the agent string with the given value. func (u *UserAgentBuilder) AddKeyValue(key, value string) { u.appendTo(key + "/" + value) } // Build returns the constructed User-Agent string. May be called multiple times. func (u *UserAgentBuilder) Build() string { return u.sb.String() } func (u *UserAgentBuilder) appendTo(value string) { if u.sb.Len() > 0 { u.sb.WriteRune(' ') } u.sb.WriteString(value) } smithy-go-1.20.3/transport/http/user_agent_test.go000066400000000000000000000003621463735525100222660ustar00rootroot00000000000000package http import "testing" func TestUserAgentBuilder(t *testing.T) { b := NewUserAgentBuilder() b.AddKeyValue("foo", "1.2.3") b.AddKey("baz") if e, a := "foo/1.2.3 baz", b.Build(); e != a { t.Errorf("expect %v, got %v", e, a) } } smithy-go-1.20.3/validation.go000066400000000000000000000071331463735525100162150ustar00rootroot00000000000000package smithy import ( "bytes" "fmt" "strings" ) // An InvalidParamsError provides wrapping of invalid parameter errors found when // validating API operation input parameters. type InvalidParamsError struct { // Context is the base context of the invalid parameter group. Context string errs []InvalidParamError } // Add adds a new invalid parameter error to the collection of invalid // parameters. The context of the invalid parameter will be updated to reflect // this collection. func (e *InvalidParamsError) Add(err InvalidParamError) { err.SetContext(e.Context) e.errs = append(e.errs, err) } // AddNested adds the invalid parameter errors from another InvalidParamsError // value into this collection. The nested errors will have their nested context // updated and base context to reflect the merging. // // Use for nested validations errors. func (e *InvalidParamsError) AddNested(nestedCtx string, nested InvalidParamsError) { for _, err := range nested.errs { err.SetContext(e.Context) err.AddNestedContext(nestedCtx) e.errs = append(e.errs, err) } } // Len returns the number of invalid parameter errors func (e *InvalidParamsError) Len() int { return len(e.errs) } // Error returns the string formatted form of the invalid parameters. func (e InvalidParamsError) Error() string { w := &bytes.Buffer{} fmt.Fprintf(w, "%d validation error(s) found.\n", len(e.errs)) for _, err := range e.errs { fmt.Fprintf(w, "- %s\n", err.Error()) } return w.String() } // Errs returns a slice of the invalid parameters func (e InvalidParamsError) Errs() []error { errs := make([]error, len(e.errs)) for i := 0; i < len(errs); i++ { errs[i] = e.errs[i] } return errs } // An InvalidParamError represents an invalid parameter error type. type InvalidParamError interface { error // Field name the error occurred on. Field() string // SetContext updates the context of the error. SetContext(string) // AddNestedContext updates the error's context to include a nested level. AddNestedContext(string) } type invalidParamError struct { context string nestedContext string field string reason string } // Error returns the string version of the invalid parameter error. func (e invalidParamError) Error() string { return fmt.Sprintf("%s, %s.", e.reason, e.Field()) } // Field Returns the field and context the error occurred. func (e invalidParamError) Field() string { sb := &strings.Builder{} sb.WriteString(e.context) if sb.Len() > 0 { if len(e.nestedContext) == 0 || (len(e.nestedContext) > 0 && e.nestedContext[:1] != "[") { sb.WriteRune('.') } } if len(e.nestedContext) > 0 { sb.WriteString(e.nestedContext) sb.WriteRune('.') } sb.WriteString(e.field) return sb.String() } // SetContext updates the base context of the error. func (e *invalidParamError) SetContext(ctx string) { e.context = ctx } // AddNestedContext prepends a context to the field's path. func (e *invalidParamError) AddNestedContext(ctx string) { if len(e.nestedContext) == 0 { e.nestedContext = ctx return } // Check if our nested context is an index into a slice or map if e.nestedContext[:1] != "[" { e.nestedContext = fmt.Sprintf("%s.%s", ctx, e.nestedContext) return } e.nestedContext = ctx + e.nestedContext } // An ParamRequiredError represents an required parameter error. type ParamRequiredError struct { invalidParamError } // NewErrParamRequired creates a new required parameter error. func NewErrParamRequired(field string) *ParamRequiredError { return &ParamRequiredError{ invalidParamError{ field: field, reason: fmt.Sprintf("missing required field"), }, } } smithy-go-1.20.3/waiter/000077500000000000000000000000001463735525100150235ustar00rootroot00000000000000smithy-go-1.20.3/waiter/logger.go000066400000000000000000000020251463735525100166300ustar00rootroot00000000000000package waiter import ( "context" "fmt" "github.com/aws/smithy-go/logging" "github.com/aws/smithy-go/middleware" ) // Logger is the Logger middleware used by the waiter to log an attempt type Logger struct { // Attempt is the current attempt to be logged Attempt int64 } // ID representing the Logger middleware func (*Logger) ID() string { return "WaiterLogger" } // HandleInitialize performs handling of request in initialize stack step func (m *Logger) HandleInitialize(ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) ( out middleware.InitializeOutput, metadata middleware.Metadata, err error, ) { logger := middleware.GetLogger(ctx) logger.Logf(logging.Debug, fmt.Sprintf("attempting waiter request, attempt count: %d", m.Attempt)) return next.HandleInitialize(ctx, in) } // AddLogger is a helper util to add waiter logger after `SetLogger` middleware in func (m Logger) AddLogger(stack *middleware.Stack) error { return stack.Initialize.Insert(&m, "SetLogger", middleware.After) } smithy-go-1.20.3/waiter/waiter.go000066400000000000000000000035661463735525100166570ustar00rootroot00000000000000package waiter import ( "fmt" "math" "time" "github.com/aws/smithy-go/rand" ) // ComputeDelay computes delay between waiter attempts. The function takes in a current attempt count, // minimum delay, maximum delay, and remaining wait time for waiter as input. The inputs minDelay and maxDelay // must always be greater than 0, along with minDelay lesser than or equal to maxDelay. // // Returns the computed delay and if next attempt count is possible within the given input time constraints. // Note that the zeroth attempt results in no delay. func ComputeDelay(attempt int64, minDelay, maxDelay, remainingTime time.Duration) (delay time.Duration, err error) { // zeroth attempt, no delay if attempt <= 0 { return 0, nil } // remainingTime is zero or less, no delay if remainingTime <= 0 { return 0, nil } // validate min delay is greater than 0 if minDelay == 0 { return 0, fmt.Errorf("minDelay must be greater than zero when computing Delay") } // validate max delay is greater than 0 if maxDelay == 0 { return 0, fmt.Errorf("maxDelay must be greater than zero when computing Delay") } // Get attempt ceiling to prevent integer overflow. attemptCeiling := (math.Log(float64(maxDelay/minDelay)) / math.Log(2)) + 1 if attempt > int64(attemptCeiling) { delay = maxDelay } else { // Compute exponential delay based on attempt. ri := 1 << uint64(attempt-1) // compute delay delay = minDelay * time.Duration(ri) } if delay != minDelay { // randomize to get jitter between min delay and delay value d, err := rand.CryptoRandInt63n(int64(delay - minDelay)) if err != nil { return 0, fmt.Errorf("error computing retry jitter, %w", err) } delay = time.Duration(d) + minDelay } // check if this is the last attempt possible and compute delay accordingly if remainingTime-delay <= minDelay { delay = remainingTime - minDelay } return delay, nil } smithy-go-1.20.3/waiter/waiter_test.go000066400000000000000000000072351463735525100177130ustar00rootroot00000000000000package waiter import ( mathrand "math/rand" "strings" "testing" "time" "github.com/aws/smithy-go/rand" ) func TestComputeDelay(t *testing.T) { cases := map[string]struct { totalAttempts int64 minDelay time.Duration maxDelay time.Duration maxWaitTime time.Duration expectedMaxDelays []time.Duration expectedError string expectedMinAttempts int }{ "standard": { totalAttempts: 8, minDelay: 2 * time.Second, maxDelay: 120 * time.Second, maxWaitTime: 300 * time.Second, expectedMaxDelays: []time.Duration{2, 4, 8, 16, 32, 64, 120, 120}, expectedMinAttempts: 8, }, "zero minDelay": { totalAttempts: 3, minDelay: 0, maxDelay: 120 * time.Second, maxWaitTime: 300 * time.Second, expectedError: "minDelay must be greater than zero", }, "zero maxDelay": { totalAttempts: 3, minDelay: 10 * time.Second, maxDelay: 0, maxWaitTime: 300 * time.Second, expectedError: "maxDelay must be greater than zero", }, "zero remaining time": { totalAttempts: 3, minDelay: 10 * time.Second, maxDelay: 20 * time.Second, maxWaitTime: 0, expectedMaxDelays: []time.Duration{0}, expectedMinAttempts: 1, }, "max wait time is less than min delay": { totalAttempts: 3, minDelay: 10 * time.Second, maxDelay: 20 * time.Second, maxWaitTime: 5 * time.Second, expectedMaxDelays: []time.Duration{0}, expectedMinAttempts: 1, }, "large minDelay": { totalAttempts: 80, minDelay: 150 * time.Minute, maxDelay: 200 * time.Minute, maxWaitTime: 250 * time.Minute, expectedMinAttempts: 1, }, "large maxDelay": { totalAttempts: 80, minDelay: 15 * time.Minute, maxDelay: 2000 * time.Minute, maxWaitTime: 250 * time.Minute, expectedMinAttempts: 5, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { // mock smithy-go rand/#Reader r := rand.Reader defer func() { rand.Reader = r }() rand.Reader = mathrand.New(mathrand.NewSource(1)) // mock waiter call delays, err := mockwait(c.totalAttempts, c.minDelay, c.maxDelay, c.maxWaitTime) if len(c.expectedError) != 0 { if err == nil { t.Fatalf("expected error, got none") } if e, a := c.expectedError, err.Error(); !strings.Contains(a, e) { t.Fatalf("expected error %v, got %v instead", e, a) } } else if err != nil { t.Fatalf("expected no error, got %v", err) } if e, a := c.expectedMinAttempts, len(delays); e > a { t.Logf("%v", delays) t.Fatalf("expected minimum attempts to be %v, got %v", e, a) } for i, expectedDelay := range c.expectedMaxDelays { if e, a := expectedDelay*time.Second, delays[i]; e < a { t.Fatalf("attempt %d : expected delay to be less than %v, got %v", i+1, e, a) } if e, a := c.minDelay, delays[i]; e > a && c.maxWaitTime > c.minDelay { t.Fatalf("attempt %d : expected delay to be more than %v, got %v", i+1, e, a) } } t.Logf("delays : %v", delays) }) } } func mockwait(maxAttempts int64, minDelay, maxDelay, maxWaitTime time.Duration) ([]time.Duration, error) { delays := make([]time.Duration, 0) remainingTime := maxWaitTime var attempt int64 for { attempt++ if maxAttempts < attempt { break } delay, err := ComputeDelay(attempt, minDelay, maxDelay, remainingTime) if err != nil { return delays, err } delays = append(delays, delay) remainingTime -= delay if remainingTime < minDelay { break } } return delays, nil }