diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b9e65ceeab..0e9dc6ab26b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ under development: Experimental Feature | Flag | Description ---------------------|-------------------------------|------------ -Identity & Auth | `experimentalIdentityAndAuth` | Standardize identity and auth integrations to match the Smithy specification (see [Authentication Traits](https://smithy.io/2.0/spec/authentication-traits.html)). Newer capabilities include support for multiple auth schemes, `@optionalAuth`, and standardized identity interfaces for authentication schemes both in code generation and TypeScript packages. In `smithy-typescript`, `@httpApiKeyAuth` will be updated to use the new standardized interfaces. In `aws-sdk-js-v3` (`smithy-typescript`'s largest customer), this will affect `@aws.auth#sigv4` and `@httpBearerAuth` implementations, but is planned to be completely backwards-compatible. +N/A | N/A | N/A ## Reporting Bugs/Feature Requests diff --git a/README.md b/README.md index 9264ce3099d..b10dda96c90 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ By default, the Smithy TypeScript code generators provide the code generation fr |`private`|No|Whether the package is `private` in `package.json`. The default value is `false`.| |`requiredMemberMode`|No|**NOT RECOMMENDED DUE TO BACKWARD COMPATIBILITY CONCERNS.** Sets whether members marked with the `@required` trait are allowed to be `undefined`. See more details on the risks in `TypeScriptSettings.RequiredMemberMode`. The default value is `nullable`.| |`createDefaultReadme`|No|Whether to generate a default `README.md` for the package. The default value is `false`.| -|`experimentalIdentityAndAuth`|No|Experimental feature that standardizes identity and auth integrations to match the Smithy specification (see [Authentication Traits](https://smithy.io/2.0/spec/authentication-traits.html)). See [the experimental features section for more details](CONTRIBUTING.md#experimental-features).| +|`useLegacyAuth`|No|**NOT RECOMMENDED, AVAILABLE ONLY FOR BACKWARD COMPATIBILITY CONCERNS.** Flag that enables using legacy auth. When in doubt, use the default identity and auth behavior (not configuring `useLegacyAuth`) as the golden path.| #### `typescript-client-codegen` plugin artifacts diff --git a/gradle.properties b/gradle.properties index d13a5407751..8578dc5541f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -smithyVersion=1.49.0 +smithyVersion=1.50.0 smithyGradleVersion=0.10.1 diff --git a/packages/config-resolver/CHANGELOG.md b/packages/config-resolver/CHANGELOG.md index c46dad022cf..a84a7707828 100644 --- a/packages/config-resolver/CHANGELOG.md +++ b/packages/config-resolver/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 3.0.5 + +### Patch Changes + +- @smithy/node-config-provider@3.1.4 + ## 3.0.4 ### Patch Changes diff --git a/packages/config-resolver/package.json b/packages/config-resolver/package.json index 249d1166d62..e0ad463e789 100644 --- a/packages/config-resolver/package.json +++ b/packages/config-resolver/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/config-resolver", - "version": "3.0.4", + "version": "3.0.5", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline config-resolver", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 6d19602c3b0..5ef1d2dade0 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,68 @@ # Change Log +## 2.3.2 + +### Patch Changes + +- Updated dependencies [670553a] + - @smithy/smithy-client@3.1.12 + - @smithy/middleware-retry@3.0.14 + +## 2.3.1 + +### Patch Changes + +- @smithy/smithy-client@3.1.11 +- @smithy/middleware-retry@3.0.13 + +## 2.3.0 + +### Minor Changes + +- 86862ea: switch to static HttpRequest clone method + +### Patch Changes + +- Updated dependencies [4a40961] +- Updated dependencies [86862ea] + - @smithy/middleware-endpoint@3.1.0 + - @smithy/protocol-http@4.1.0 + - @smithy/smithy-client@3.1.10 + - @smithy/middleware-retry@3.0.12 + - @smithy/middleware-serde@3.0.3 + +## 2.2.8 + +### Patch Changes + +- @smithy/smithy-client@3.1.9 +- @smithy/middleware-retry@3.0.11 + +## 2.2.7 + +### Patch Changes + +- Updated dependencies [796567d] + - @smithy/protocol-http@4.0.4 + - @smithy/middleware-retry@3.0.10 + - @smithy/smithy-client@3.1.8 + - @smithy/middleware-serde@3.0.3 + +## 2.2.6 + +### Patch Changes + +- @smithy/middleware-endpoint@3.0.5 +- @smithy/smithy-client@3.1.7 +- @smithy/middleware-retry@3.0.9 + +## 2.2.5 + +### Patch Changes + +- @smithy/smithy-client@3.1.6 +- @smithy/middleware-retry@3.0.8 + ## 2.2.4 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 38181c36f2c..119c73e7deb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/core", - "version": "2.2.4", + "version": "2.3.2", "scripts": { "build": "yarn lint && concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline core", diff --git a/packages/core/src/util-identity-and-auth/httpAuthSchemes/httpApiKeyAuth.ts b/packages/core/src/util-identity-and-auth/httpAuthSchemes/httpApiKeyAuth.ts index 6b0ee66d777..1b2aa329867 100644 --- a/packages/core/src/util-identity-and-auth/httpAuthSchemes/httpApiKeyAuth.ts +++ b/packages/core/src/util-identity-and-auth/httpAuthSchemes/httpApiKeyAuth.ts @@ -24,7 +24,7 @@ export class HttpApiKeyAuthSigner implements HttpSigner { if (!identity.apiKey) { throw new Error("request could not be signed with `apiKey` since the `apiKey` is not defined"); } - const clonedRequest = httpRequest.clone(); + const clonedRequest = HttpRequest.clone(httpRequest); if (signingProperties.in === HttpApiKeyAuthLocation.QUERY) { clonedRequest.query[signingProperties.name] = identity.apiKey; } else if (signingProperties.in === HttpApiKeyAuthLocation.HEADER) { diff --git a/packages/core/src/util-identity-and-auth/httpAuthSchemes/httpBearerAuth.ts b/packages/core/src/util-identity-and-auth/httpAuthSchemes/httpBearerAuth.ts index f4c6f120afc..7d2c5fef3d2 100644 --- a/packages/core/src/util-identity-and-auth/httpAuthSchemes/httpBearerAuth.ts +++ b/packages/core/src/util-identity-and-auth/httpAuthSchemes/httpBearerAuth.ts @@ -11,7 +11,7 @@ export class HttpBearerAuthSigner implements HttpSigner { identity: TokenIdentity, signingProperties: Record ): Promise { - const clonedRequest = httpRequest.clone(); + const clonedRequest = HttpRequest.clone(httpRequest); if (!identity.token) { throw new Error("request could not be signed with `token` since the `token` is not defined"); } diff --git a/packages/credential-provider-imds/CHANGELOG.md b/packages/credential-provider-imds/CHANGELOG.md index 768e3e76872..b704770c407 100644 --- a/packages/credential-provider-imds/CHANGELOG.md +++ b/packages/credential-provider-imds/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 3.2.0 + +### Minor Changes + +- 3d72b04: sources accountId from IMDS + +## 3.1.4 + +### Patch Changes + +- @smithy/node-config-provider@3.1.4 + ## 3.1.3 ### Patch Changes diff --git a/packages/credential-provider-imds/package.json b/packages/credential-provider-imds/package.json index 962d52eba78..4b78aefa437 100644 --- a/packages/credential-provider-imds/package.json +++ b/packages/credential-provider-imds/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/credential-provider-imds", - "version": "3.1.3", + "version": "3.2.0", "description": "AWS credential provider that sources credentials from the EC2 instance metadata service and ECS container metadata service", "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", diff --git a/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.spec.ts b/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.spec.ts index 21dd651079d..53e8aecfab0 100644 --- a/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.spec.ts +++ b/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.spec.ts @@ -7,11 +7,15 @@ const creds: ImdsCredentials = Object.freeze({ SecretAccessKey: "bar", Token: "baz", Expiration: new Date().toISOString(), + AccountId: "123456789012", }); describe("isImdsCredentials", () => { it("should accept valid ImdsCredentials objects", () => { expect(isImdsCredentials(creds)).toBe(true); + const { AccountId, ...credsWithoutAccountId } = creds; + expect(AccountId).toBe("123456789012"); + expect(isImdsCredentials(credsWithoutAccountId)).toBe(true); }); it("should reject credentials without an AccessKeyId", () => { @@ -44,5 +48,22 @@ describe("fromImdsCredentials", () => { expect(converted.secretAccessKey).toEqual(creds.SecretAccessKey); expect(converted.sessionToken).toEqual(creds.Token); expect(converted.expiration).toEqual(new Date(creds.Expiration)); + expect(converted.accountId).toEqual(creds.AccountId); + }); + + it("should convert IMDS credentials to a credentials object without accountId when it's not provided", () => { + const credsWithoutAccountId: ImdsCredentials = { + AccessKeyId: "foo", + SecretAccessKey: "bar", + Token: "baz", + Expiration: new Date().toISOString(), + // AccountId is omitted + }; + const converted: AwsCredentialIdentity = fromImdsCredentials(credsWithoutAccountId); + expect(converted.accessKeyId).toEqual(credsWithoutAccountId.AccessKeyId); + expect(converted.secretAccessKey).toEqual(credsWithoutAccountId.SecretAccessKey); + expect(converted.sessionToken).toEqual(credsWithoutAccountId.Token); + expect(converted.expiration).toEqual(new Date(credsWithoutAccountId.Expiration)); + expect(converted.accountId).toBeUndefined(); // Verify accountId is undefined }); }); diff --git a/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.ts b/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.ts index fc5b1d36c2c..53cc160db1d 100644 --- a/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.ts +++ b/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.ts @@ -8,6 +8,7 @@ export interface ImdsCredentials { SecretAccessKey: string; Token: string; Expiration: string; + AccountId?: string; } /** @@ -29,4 +30,5 @@ export const fromImdsCredentials = (creds: ImdsCredentials): AwsCredentialIdenti secretAccessKey: creds.SecretAccessKey, sessionToken: creds.Token, expiration: new Date(creds.Expiration), + ...(creds.AccountId && { accountId: creds.AccountId }), }); diff --git a/packages/eventstream-serde-browser/CHANGELOG.md b/packages/eventstream-serde-browser/CHANGELOG.md index 850749db9e6..2abb30e6b1e 100644 --- a/packages/eventstream-serde-browser/CHANGELOG.md +++ b/packages/eventstream-serde-browser/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 3.0.5 + +### Patch Changes + +- 1cfe243: avoid compilation of global ReadableStream with type parameter + ## 3.0.4 ### Patch Changes diff --git a/packages/eventstream-serde-browser/package.json b/packages/eventstream-serde-browser/package.json index 4020958a44e..311fe1105c7 100644 --- a/packages/eventstream-serde-browser/package.json +++ b/packages/eventstream-serde-browser/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/eventstream-serde-browser", - "version": "3.0.4", + "version": "3.0.5", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline eventstream-serde-browser", diff --git a/packages/eventstream-serde-browser/src/EventStreamMarshaller.ts b/packages/eventstream-serde-browser/src/EventStreamMarshaller.ts index 0261f2131fb..4ba40db85c7 100644 --- a/packages/eventstream-serde-browser/src/EventStreamMarshaller.ts +++ b/packages/eventstream-serde-browser/src/EventStreamMarshaller.ts @@ -68,5 +68,11 @@ export class EventStreamMarshaller { } } +/** + * @internal + * Warning: do not export this without aliasing the reference to + * global ReadableStream. + * @see https://github.com/smithy-lang/smithy-typescript/issues/1341. + */ const isReadableStream = (body: any): body is ReadableStream => typeof ReadableStream === "function" && body instanceof ReadableStream; diff --git a/packages/experimental-identity-and-auth/CHANGELOG.md b/packages/experimental-identity-and-auth/CHANGELOG.md index 2dfddf9ec24..f177e6fa5d3 100644 --- a/packages/experimental-identity-and-auth/CHANGELOG.md +++ b/packages/experimental-identity-and-auth/CHANGELOG.md @@ -1,5 +1,66 @@ # Change Log +## 0.3.16 + +### Patch Changes + +- @smithy/middleware-retry@3.0.14 + +## 0.3.15 + +### Patch Changes + +- a40e1e9: set identity&auth SRA active by default + +## 0.3.14 + +### Patch Changes + +- @smithy/middleware-retry@3.0.13 + +## 0.3.13 + +### Patch Changes + +- 86862ea: switch to static HttpRequest clone method +- Updated dependencies [4a40961] +- Updated dependencies [86862ea] + - @smithy/middleware-endpoint@3.1.0 + - @smithy/protocol-http@4.1.0 + - @smithy/signature-v4@4.1.0 + - @smithy/middleware-retry@3.0.12 + - @smithy/middleware-serde@3.0.3 + +## 0.3.12 + +### Patch Changes + +- @smithy/middleware-retry@3.0.11 + +## 0.3.11 + +### Patch Changes + +- Updated dependencies [796567d] +- Updated dependencies [ae8bf5c] + - @smithy/protocol-http@4.0.4 + - @smithy/signature-v4@4.0.0 + - @smithy/middleware-retry@3.0.10 + - @smithy/middleware-serde@3.0.3 + +## 0.3.10 + +### Patch Changes + +- @smithy/middleware-endpoint@3.0.5 +- @smithy/middleware-retry@3.0.9 + +## 0.3.9 + +### Patch Changes + +- @smithy/middleware-retry@3.0.8 + ## 0.3.8 ### Patch Changes diff --git a/packages/experimental-identity-and-auth/package.json b/packages/experimental-identity-and-auth/package.json index b30be01efb8..141ab4e1eb6 100644 --- a/packages/experimental-identity-and-auth/package.json +++ b/packages/experimental-identity-and-auth/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/experimental-identity-and-auth", - "version": "0.3.8", + "version": "0.3.16", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline experimental-identity-and-auth", diff --git a/packages/experimental-identity-and-auth/src/SigV4Signer.ts b/packages/experimental-identity-and-auth/src/SigV4Signer.ts index 558772079cb..34afaae403b 100644 --- a/packages/experimental-identity-and-auth/src/SigV4Signer.ts +++ b/packages/experimental-identity-and-auth/src/SigV4Signer.ts @@ -13,7 +13,7 @@ export class SigV4Signer implements HttpSigner { identity: AwsCredentialIdentity, signingProperties: Record ): Promise { - const clonedRequest = httpRequest.clone(); + const clonedRequest = HttpRequest.clone(httpRequest); const signer = new SignatureV4({ applyChecksum: signingProperties.applyChecksum !== undefined ? signingProperties.applyChecksum : true, credentials: identity, diff --git a/packages/experimental-identity-and-auth/src/httpApiKeyAuth.ts b/packages/experimental-identity-and-auth/src/httpApiKeyAuth.ts index e8c0087db79..ca72570a3b9 100644 --- a/packages/experimental-identity-and-auth/src/httpApiKeyAuth.ts +++ b/packages/experimental-identity-and-auth/src/httpApiKeyAuth.ts @@ -35,7 +35,7 @@ export class HttpApiKeyAuthSigner implements HttpSigner { if (!identity.apiKey) { throw new Error("request could not be signed with `apiKey` since the `apiKey` is not defined"); } - const clonedRequest = httpRequest.clone(); + const clonedRequest = HttpRequest.clone(httpRequest); if (signingProperties.in === HttpApiKeyAuthLocation.QUERY) { clonedRequest.query[signingProperties.name] = identity.apiKey; } else if (signingProperties.in === HttpApiKeyAuthLocation.HEADER) { diff --git a/packages/experimental-identity-and-auth/src/httpBearerAuth.ts b/packages/experimental-identity-and-auth/src/httpBearerAuth.ts index 0ffe86c3fa1..249f54d29a6 100644 --- a/packages/experimental-identity-and-auth/src/httpBearerAuth.ts +++ b/packages/experimental-identity-and-auth/src/httpBearerAuth.ts @@ -14,7 +14,7 @@ export class HttpBearerAuthSigner implements HttpSigner { identity: TokenIdentity, signingProperties: Record ): Promise { - const clonedRequest = httpRequest.clone(); + const clonedRequest = HttpRequest.clone(httpRequest); if (!identity.token) { throw new Error("request could not be signed with `token` since the `token` is not defined"); } diff --git a/packages/experimental-identity-and-auth/src/integration/httpApiKeyAuth.integ.spec.ts b/packages/experimental-identity-and-auth/src/integration/httpApiKeyAuth.integ.spec.ts index 9ab8efa9805..496bd51236e 100644 --- a/packages/experimental-identity-and-auth/src/integration/httpApiKeyAuth.integ.spec.ts +++ b/packages/experimental-identity-and-auth/src/integration/httpApiKeyAuth.integ.spec.ts @@ -7,7 +7,7 @@ import { import { requireRequestsFrom } from "@smithy/util-test"; describe("@httpApiKeyAuth integration tests", () => { - // TODO(experimentalIdentityAndAuth): should match `HttpApiKeyAuthService` `@httpApiKeyAuth` trait + // Match `HttpApiKeyAuthService` `@httpApiKeyAuth` trait const MOCK_API_KEY_NAME = "Authorization"; const MOCK_API_KEY_SCHEME = "ApiKey"; const MOCK_API_KEY = "APIKEY_123"; diff --git a/packages/fetch-http-handler/CHANGELOG.md b/packages/fetch-http-handler/CHANGELOG.md index 63b3ef5a46c..3c08be00708 100644 --- a/packages/fetch-http-handler/CHANGELOG.md +++ b/packages/fetch-http-handler/CHANGELOG.md @@ -1,5 +1,31 @@ # Change Log +## 3.2.4 + +### Patch Changes + +- 3ea4789: Initialize removeSignalEventListener as an empty function + +## 3.2.3 + +### Patch Changes + +- Updated dependencies [86862ea] + - @smithy/protocol-http@4.1.0 + +## 3.2.2 + +### Patch Changes + +- Updated dependencies [796567d] + - @smithy/protocol-http@4.0.4 + +## 3.2.1 + +### Patch Changes + +- f31cc5f: remove abort signal event listeners after request completion + ## 3.2.0 ### Minor Changes diff --git a/packages/fetch-http-handler/package.json b/packages/fetch-http-handler/package.json index b4c2660ace2..8d876a057b6 100644 --- a/packages/fetch-http-handler/package.json +++ b/packages/fetch-http-handler/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/fetch-http-handler", - "version": "3.2.0", + "version": "3.2.4", "description": "Provides a way to make requests", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", diff --git a/packages/fetch-http-handler/src/fetch-http-handler.ts b/packages/fetch-http-handler/src/fetch-http-handler.ts index d9c17fcfb0f..81995ce978d 100644 --- a/packages/fetch-http-handler/src/fetch-http-handler.ts +++ b/packages/fetch-http-handler/src/fetch-http-handler.ts @@ -127,6 +127,8 @@ export class FetchHttpHandler implements HttpHandler { requestOptions.keepalive = keepAlive; } + let removeSignalEventListener = () => {}; + const fetchRequest = new Request(url, requestOptions); const raceOfPromises = [ fetch(fetchRequest).then((response) => { @@ -173,7 +175,9 @@ export class FetchHttpHandler implements HttpHandler { }; if (typeof (abortSignal as AbortSignal).addEventListener === "function") { // preferred. - (abortSignal as AbortSignal).addEventListener("abort", onAbort); + const signal = abortSignal as AbortSignal; + signal.addEventListener("abort", onAbort, { once: true }); + removeSignalEventListener = () => signal.removeEventListener("abort", onAbort); } else { // backwards compatibility abortSignal.onabort = onAbort; @@ -181,7 +185,7 @@ export class FetchHttpHandler implements HttpHandler { }) ); } - return Promise.race(raceOfPromises); + return Promise.race(raceOfPromises).finally(removeSignalEventListener); } updateHttpClientConfig(key: keyof FetchHttpHandlerConfig, value: FetchHttpHandlerConfig[typeof key]): void { diff --git a/packages/middleware-apply-body-checksum/CHANGELOG.md b/packages/middleware-apply-body-checksum/CHANGELOG.md index 6c2514de1d2..9778ffa1e48 100644 --- a/packages/middleware-apply-body-checksum/CHANGELOG.md +++ b/packages/middleware-apply-body-checksum/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## 3.0.5 + +### Patch Changes + +- 9624938: Fix request copying with `HttpRequest.clone()`. +- Updated dependencies [86862ea] + - @smithy/protocol-http@4.1.0 + +## 3.0.4 + +### Patch Changes + +- Updated dependencies [796567d] + - @smithy/protocol-http@4.0.4 + ## 3.0.3 ### Patch Changes diff --git a/packages/middleware-apply-body-checksum/package.json b/packages/middleware-apply-body-checksum/package.json index 7f031546e02..b5f8210de4b 100644 --- a/packages/middleware-apply-body-checksum/package.json +++ b/packages/middleware-apply-body-checksum/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/middleware-apply-body-checksum", - "version": "3.0.3", + "version": "3.0.5", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline middleware-apply-body-checksum", diff --git a/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.spec.ts b/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.spec.ts index 31bf6a45111..68112ea2247 100644 --- a/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.spec.ts +++ b/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.spec.ts @@ -73,6 +73,27 @@ describe("applyMd5BodyChecksumMiddleware", () => { expect(mockHashDigest.mock.calls.length).toBe(0); expect(mockEncoder.mock.calls.length).toBe(0); }); + + it("should clone the request when applying the checksum", async () => { + const handler = applyMd5BodyChecksumMiddleware({ + md5: MockHash, + base64Encoder: mockEncoder, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + streamHasher: async (stream: ExoticStream) => new Uint8Array(5), + })(next, {} as any); + + await handler({ + input: {}, + request: new HttpRequest({ + body: body, + }), + }); + + expect(next.mock.calls.length).toBe(1); + const { request } = next.mock.calls[0][0]; + // Assert that non-enumerable properties like the method `clone()` are preserved. + expect(request.clone).toBeDefined(); + }); } it("should use the supplied stream hasher to calculate the hash of a streaming body", async () => { diff --git a/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.ts b/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.ts index 1abd166a10f..e2d9e8f17f9 100644 --- a/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.ts +++ b/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.ts @@ -17,7 +17,7 @@ export const applyMd5BodyChecksumMiddleware = (options: Md5BodyChecksumResolvedConfig): BuildMiddleware => (next: BuildHandler): BuildHandler => async (args: BuildHandlerArguments): Promise> => { - let { request } = args; + const { request } = args; if (HttpRequest.isInstance(request)) { const { body, headers } = request; if (!hasHeader("content-md5", headers)) { @@ -30,19 +30,18 @@ export const applyMd5BodyChecksumMiddleware = digest = options.streamHasher(options.md5, body); } - request = { - ...request, - headers: { - ...headers, - "content-md5": options.base64Encoder(await digest), - }, + const cloned = HttpRequest.clone(request); + cloned.headers = { + ...headers, + "content-md5": options.base64Encoder(await digest), }; + return next({ + ...args, + request: cloned, + }); } } - return next({ - ...args, - request, - }); + return next(args); }; export const applyMd5BodyChecksumMiddlewareOptions: BuildHandlerOptions = { diff --git a/packages/middleware-compression/CHANGELOG.md b/packages/middleware-compression/CHANGELOG.md index 513038a5314..92ab0676d0c 100644 --- a/packages/middleware-compression/CHANGELOG.md +++ b/packages/middleware-compression/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## 3.0.7 + +### Patch Changes + +- Updated dependencies [86862ea] + - @smithy/protocol-http@4.1.0 + +## 3.0.6 + +### Patch Changes + +- Updated dependencies [796567d] + - @smithy/protocol-http@4.0.4 + +## 3.0.5 + +### Patch Changes + +- @smithy/node-config-provider@3.1.4 + ## 3.0.4 ### Patch Changes diff --git a/packages/middleware-compression/package.json b/packages/middleware-compression/package.json index d490812cf01..44d01db5203 100644 --- a/packages/middleware-compression/package.json +++ b/packages/middleware-compression/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/middleware-compression", - "version": "3.0.4", + "version": "3.0.7", "description": "Middleware and Plugin for request compression.", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types'", diff --git a/packages/middleware-content-length/CHANGELOG.md b/packages/middleware-content-length/CHANGELOG.md index 6231de6f57c..326d87fcec4 100644 --- a/packages/middleware-content-length/CHANGELOG.md +++ b/packages/middleware-content-length/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +## 3.0.5 + +### Patch Changes + +- Updated dependencies [86862ea] + - @smithy/protocol-http@4.1.0 + +## 3.0.4 + +### Patch Changes + +- Updated dependencies [796567d] + - @smithy/protocol-http@4.0.4 + ## 3.0.3 ### Patch Changes diff --git a/packages/middleware-content-length/package.json b/packages/middleware-content-length/package.json index 00e2bda1f29..18238538566 100644 --- a/packages/middleware-content-length/package.json +++ b/packages/middleware-content-length/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/middleware-content-length", - "version": "3.0.3", + "version": "3.0.5", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline middleware-content-length", diff --git a/packages/middleware-content-length/src/middleware-content-length.integ.spec.ts b/packages/middleware-content-length/src/middleware-content-length.integ.spec.ts index 8e6c5743bfc..b0e4242ae1e 100644 --- a/packages/middleware-content-length/src/middleware-content-length.integ.spec.ts +++ b/packages/middleware-content-length/src/middleware-content-length.integ.spec.ts @@ -29,8 +29,6 @@ describe("middleware-content-length", () => { }, }); - console.log(client.middlewareStack); - await client.createCity({ name: "MyCity", coordinates: { diff --git a/packages/middleware-endpoint/CHANGELOG.md b/packages/middleware-endpoint/CHANGELOG.md index 738f225dd77..05f9c68c432 100644 --- a/packages/middleware-endpoint/CHANGELOG.md +++ b/packages/middleware-endpoint/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## 3.1.0 + +### Minor Changes + +- 4a40961: add support for accountId in configValueProvider + +### Patch Changes + +- @smithy/middleware-serde@3.0.3 + +## 3.0.5 + +### Patch Changes + +- Updated dependencies [d88521e] + - @smithy/shared-ini-file-loader@3.1.4 + - @smithy/node-config-provider@3.1.4 + ## 3.0.4 ### Patch Changes diff --git a/packages/middleware-endpoint/package.json b/packages/middleware-endpoint/package.json index 729597b84cc..4e897db338a 100644 --- a/packages/middleware-endpoint/package.json +++ b/packages/middleware-endpoint/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/middleware-endpoint", - "version": "3.0.4", + "version": "3.1.0", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline middleware-endpoint", diff --git a/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.spec.ts b/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.spec.ts index dbc54f35106..5ec1674a2ef 100644 --- a/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.spec.ts +++ b/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.spec.ts @@ -31,6 +31,17 @@ describe(createConfigValueProvider.name, () => { expect(await createConfigValueProvider("credentialScope", "CredentialScope", config)()).toEqual("cred-scope"); }); + it("uses a special lookup for accountId", async () => { + const config = { + credentials: async () => { + return { + accountId: "123456789012", + }; + }, + }; + expect(await createConfigValueProvider("accountId", "AccountId", config)()).toEqual("123456789012"); + }); + it("should normalize endpoint objects into URLs", async () => { const sampleUrl = "https://aws.amazon.com/"; const config = { diff --git a/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.ts b/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.ts index f376a52587c..f2726f81b1a 100644 --- a/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.ts +++ b/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.ts @@ -31,6 +31,15 @@ export const createConfigValueProvider = return configValue; }; } + + if (configKey === "accountId" || canonicalEndpointParamKey === "AccountId") { + return async () => { + const credentials = typeof config.credentials === "function" ? await config.credentials() : config.credentials; + const configValue: string = credentials?.accountId ?? credentials?.AccountId; + return configValue; + }; + } + if (configKey === "endpoint" || canonicalEndpointParamKey === "endpoint") { return async () => { const endpoint = await configProvider(); diff --git a/packages/middleware-retry/CHANGELOG.md b/packages/middleware-retry/CHANGELOG.md index 259283c2390..a44683b4e2c 100644 --- a/packages/middleware-retry/CHANGELOG.md +++ b/packages/middleware-retry/CHANGELOG.md @@ -1,5 +1,53 @@ # Change Log +## 3.0.14 + +### Patch Changes + +- Updated dependencies [670553a] + - @smithy/smithy-client@3.1.12 + +## 3.0.13 + +### Patch Changes + +- @smithy/smithy-client@3.1.11 + +## 3.0.12 + +### Patch Changes + +- Updated dependencies [86862ea] + - @smithy/protocol-http@4.1.0 + - @smithy/smithy-client@3.1.10 + +## 3.0.11 + +### Patch Changes + +- @smithy/smithy-client@3.1.9 + +## 3.0.10 + +### Patch Changes + +- Updated dependencies [796567d] + - @smithy/protocol-http@4.0.4 + - @smithy/smithy-client@3.1.8 + +## 3.0.9 + +### Patch Changes + +- @smithy/node-config-provider@3.1.4 +- @smithy/smithy-client@3.1.7 + +## 3.0.8 + +### Patch Changes + +- @smithy/smithy-client@3.1.6 + ## 3.0.7 ### Patch Changes diff --git a/packages/middleware-retry/package.json b/packages/middleware-retry/package.json index f48c4fc9ca1..4c7956510f8 100644 --- a/packages/middleware-retry/package.json +++ b/packages/middleware-retry/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/middleware-retry", - "version": "3.0.7", + "version": "3.0.14", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline middleware-retry", diff --git a/packages/node-config-provider/CHANGELOG.md b/packages/node-config-provider/CHANGELOG.md index fbbd7842b27..8b78dd37dd2 100644 --- a/packages/node-config-provider/CHANGELOG.md +++ b/packages/node-config-provider/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## 3.1.4 + +### Patch Changes + +- Updated dependencies [d88521e] + - @smithy/shared-ini-file-loader@3.1.4 + ## 3.1.3 ### Patch Changes diff --git a/packages/node-config-provider/package.json b/packages/node-config-provider/package.json index 0578327591b..2349f2ca9f5 100644 --- a/packages/node-config-provider/package.json +++ b/packages/node-config-provider/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/node-config-provider", - "version": "3.1.3", + "version": "3.1.4", "description": "Load config default values from ini config files and environmental variable", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", diff --git a/packages/node-http-handler/CHANGELOG.md b/packages/node-http-handler/CHANGELOG.md index 17cfcc539bd..09fd43ed133 100644 --- a/packages/node-http-handler/CHANGELOG.md +++ b/packages/node-http-handler/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## 3.1.4 + +### Patch Changes + +- Updated dependencies [86862ea] + - @smithy/protocol-http@4.1.0 + +## 3.1.3 + +### Patch Changes + +- Updated dependencies [796567d] + - @smithy/protocol-http@4.0.4 + +## 3.1.2 + +### Patch Changes + +- f31cc5f: remove abort signal event listeners after request completion + ## 3.1.1 ### Patch Changes diff --git a/packages/node-http-handler/package.json b/packages/node-http-handler/package.json index d7f9ac73313..704dc6eb3fd 100644 --- a/packages/node-http-handler/package.json +++ b/packages/node-http-handler/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/node-http-handler", - "version": "3.1.1", + "version": "3.1.4", "description": "Provides a way to make requests", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", diff --git a/packages/node-http-handler/src/node-http-handler.ts b/packages/node-http-handler/src/node-http-handler.ts index 0b8b12a40ff..cf619ab310e 100644 --- a/packages/node-http-handler/src/node-http-handler.ts +++ b/packages/node-http-handler/src/node-http-handler.ts @@ -252,7 +252,9 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf }; if (typeof (abortSignal as AbortSignal).addEventListener === "function") { // preferred. - (abortSignal as AbortSignal).addEventListener("abort", onAbort); + const signal = abortSignal as AbortSignal; + signal.addEventListener("abort", onAbort, { once: true }); + req.once("close", () => signal.removeEventListener("abort", onAbort)); } else { // backwards compatibility abortSignal.onabort = onAbort; diff --git a/packages/node-http-handler/src/node-http2-handler.ts b/packages/node-http-handler/src/node-http2-handler.ts index a32df5279a3..a830fa04450 100644 --- a/packages/node-http-handler/src/node-http2-handler.ts +++ b/packages/node-http-handler/src/node-http2-handler.ts @@ -189,7 +189,9 @@ export class NodeHttp2Handler implements HttpHandler { }; if (typeof (abortSignal as AbortSignal).addEventListener === "function") { // preferred. - (abortSignal as AbortSignal).addEventListener("abort", onAbort); + const signal = abortSignal as AbortSignal; + signal.addEventListener("abort", onAbort, { once: true }); + req.once("close", () => signal.removeEventListener("abort", onAbort)); } else { // backwards compatibility abortSignal.onabort = onAbort; diff --git a/packages/protocol-http/CHANGELOG.md b/packages/protocol-http/CHANGELOG.md index 609fea61506..c8f2a61ae80 100644 --- a/packages/protocol-http/CHANGELOG.md +++ b/packages/protocol-http/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 4.1.0 + +### Minor Changes + +- 86862ea: switch to static HttpRequest clone method + +## 4.0.4 + +### Patch Changes + +- 796567d: add guidance for HttpRequest cloning + ## 4.0.3 ### Patch Changes diff --git a/packages/protocol-http/package.json b/packages/protocol-http/package.json index 3fdcf8a0936..8fcf6c09e79 100644 --- a/packages/protocol-http/package.json +++ b/packages/protocol-http/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/protocol-http", - "version": "4.0.3", + "version": "4.1.0", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline protocol-http", diff --git a/packages/protocol-http/src/httpRequest.spec.ts b/packages/protocol-http/src/httpRequest.spec.ts new file mode 100644 index 00000000000..0ecd6ec54fd --- /dev/null +++ b/packages/protocol-http/src/httpRequest.spec.ts @@ -0,0 +1,116 @@ +import { QueryParameterBag } from "@smithy/types"; + +import { HttpRequest, IHttpRequest } from "./httpRequest"; + +describe("HttpRequest", () => { + const httpRequest: IHttpRequest = { + headers: { + hKey: "header-value", + }, + query: { + qKey: "query-value", + }, + method: "GET", + protocol: "https", + hostname: "localhost", + path: "/", + body: [], + }; + + it("should statically clone with deep-cloned headers/query but shallow cloned body", () => { + const httpRequest: IHttpRequest = { + headers: { + hKey: "header-value", + }, + query: { + qKey: "query-value", + }, + method: "GET", + protocol: "https", + hostname: "localhost", + path: "/", + body: [], + }; + + const clone1 = HttpRequest.clone(httpRequest); + const clone2 = HttpRequest.clone(httpRequest); + + expect(new HttpRequest(httpRequest)).toEqual(clone1); + expect(clone1).toEqual(clone2); + + expect(clone1.query).not.toBe(clone2.query); + expect(clone1.headers).not.toBe(clone2.headers); + expect(clone1.body).toBe(clone2.body); + }); + + it("should maintain a deprecated instance clone method", () => { + const httpRequestInstance = new HttpRequest(httpRequest); + + const clone1 = HttpRequest.clone(httpRequestInstance); + const clone2 = HttpRequest.clone(httpRequestInstance); + + expect(httpRequestInstance).toEqual(clone1); + expect(clone1).toEqual(clone2); + + expect(clone1.query).not.toBe(clone2.query); + expect(clone1.headers).not.toBe(clone2.headers); + expect(clone1.body).toBe(clone2.body); + }); +}); + +const cloneRequest = HttpRequest.clone; + +describe("cloneRequest", () => { + const request: IHttpRequest = Object.freeze({ + method: "GET", + protocol: "https:", + hostname: "foo.us-west-2.amazonaws.com", + path: "/", + headers: Object.freeze({ + foo: "bar", + compound: "value 1, value 2", + }), + query: Object.freeze({ + fizz: "buzz", + snap: ["crackle", "pop"], + }), + }); + + it("should return an object matching the provided request", () => { + expect(cloneRequest(request)).toEqual(request); + }); + + it("should return an object that with a different identity", () => { + expect(cloneRequest(request)).not.toBe(request); + }); + + it("should should deep-copy the headers", () => { + const clone = cloneRequest(request); + + delete clone.headers.compound; + expect(Object.keys(request.headers)).toEqual(["foo", "compound"]); + expect(Object.keys(clone.headers)).toEqual(["foo"]); + }); + + it("should should deep-copy the query", () => { + const clone = cloneRequest(request); + + const { snap } = clone.query as QueryParameterBag; + (snap as Array).shift(); + + expect((request.query as QueryParameterBag).snap).toEqual(["crackle", "pop"]); + expect((clone.query as QueryParameterBag).snap).toEqual(["pop"]); + }); + + it("should not copy the body", () => { + const body = new Uint8Array(16); + const req = { ...request, body }; + const clone = cloneRequest(req); + + expect(clone.body).toBe(req.body); + }); + + it("should handle requests without defined query objects", () => { + expect(cloneRequest({ ...request, query: void 0 }).query).toEqual({}); + }); +}); diff --git a/packages/protocol-http/src/httpRequest.ts b/packages/protocol-http/src/httpRequest.ts index cf5b08063b5..ad95c879eb6 100644 --- a/packages/protocol-http/src/httpRequest.ts +++ b/packages/protocol-http/src/httpRequest.ts @@ -3,8 +3,24 @@ import { HeaderBag, HttpMessage, HttpRequest as IHttpRequest, QueryParameterBag, type HttpRequestOptions = Partial & Partial & { method?: string }; +/** + * Use the distinct IHttpRequest interface from @smithy/types instead. + * This should not be used due to + * overlapping with the concrete class' name. + * + * This is not marked deprecated since that would mark the concrete class + * deprecated as well. + */ export interface HttpRequest extends IHttpRequest {} +/** + * @public + */ +export { IHttpRequest }; + +/** + * @public + */ export class HttpRequest implements HttpMessage, URI { public method: string; public protocol: string; @@ -18,7 +34,7 @@ export class HttpRequest implements HttpMessage, URI { public fragment?: string; public body?: any; - constructor(options: HttpRequestOptions) { + public constructor(options: HttpRequestOptions) { this.method = options.method || "GET"; this.hostname = options.hostname || "localhost"; this.port = options.port; @@ -36,9 +52,31 @@ export class HttpRequest implements HttpMessage, URI { this.fragment = options.fragment; } - static isInstance(request: unknown): request is HttpRequest { - //determine if request is a valid httpRequest - if (!request) return false; + /** + * Note: this does not deep-clone the body. + */ + public static clone(request: IHttpRequest) { + const cloned = new HttpRequest({ + ...request, + headers: { ...request.headers }, + }); + if (cloned.query) { + cloned.query = cloneQuery(cloned.query); + } + return cloned; + } + + /** + * This method only actually asserts that request is the interface {@link IHttpRequest}, + * and not necessarily this concrete class. Left in place for API stability. + * + * Do not call instance methods on the input of this function, and + * do not assume it has the HttpRequest prototype. + */ + public static isInstance(request: unknown): request is HttpRequest { + if (!request) { + return false; + } const req: any = request; return ( "method" in req && @@ -50,13 +88,13 @@ export class HttpRequest implements HttpMessage, URI { ); } - clone(): HttpRequest { - const cloned = new HttpRequest({ - ...this, - headers: { ...this.headers }, - }); - if (cloned.query) cloned.query = cloneQuery(cloned.query); - return cloned; + /** + * @deprecated use static HttpRequest.clone(request) instead. It's not safe to call + * this method because {@link HttpRequest.isInstance} incorrectly + * asserts that IHttpRequest (interface) objects are of type HttpRequest (class). + */ + public clone(): HttpRequest { + return HttpRequest.clone(this); } } diff --git a/packages/shared-ini-file-loader/CHANGELOG.md b/packages/shared-ini-file-loader/CHANGELOG.md index b083782213a..5dd4434a16c 100644 --- a/packages/shared-ini-file-loader/CHANGELOG.md +++ b/packages/shared-ini-file-loader/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 3.1.4 + +### Patch Changes + +- d88521e: read config files from paths relative to homedir + ## 3.1.3 ### Patch Changes diff --git a/packages/shared-ini-file-loader/package.json b/packages/shared-ini-file-loader/package.json index 8ab26d94895..636c6d479cc 100644 --- a/packages/shared-ini-file-loader/package.json +++ b/packages/shared-ini-file-loader/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/shared-ini-file-loader", - "version": "3.1.3", + "version": "3.1.4", "dependencies": { "@smithy/types": "workspace:^", "tslib": "^2.6.2" diff --git a/packages/shared-ini-file-loader/src/loadSharedConfigFiles.spec.ts b/packages/shared-ini-file-loader/src/loadSharedConfigFiles.spec.ts index b966188d055..1ff7e332dc8 100644 --- a/packages/shared-ini-file-loader/src/loadSharedConfigFiles.spec.ts +++ b/packages/shared-ini-file-loader/src/loadSharedConfigFiles.spec.ts @@ -1,6 +1,7 @@ import { getConfigData } from "./getConfigData"; import { getConfigFilepath } from "./getConfigFilepath"; import { getCredentialsFilepath } from "./getCredentialsFilepath"; +import { getHomeDir } from "./getHomeDir"; import { loadSharedConfigFiles } from "./loadSharedConfigFiles"; import { parseIni } from "./parseIni"; import { slurpFile } from "./slurpFile"; @@ -10,6 +11,7 @@ jest.mock("./getConfigFilepath"); jest.mock("./getCredentialsFilepath"); jest.mock("./parseIni"); jest.mock("./slurpFile"); +jest.mock("./getHomeDir"); describe("loadSharedConfigFiles", () => { const mockConfigFilepath = "/mock/file/path/config"; @@ -18,6 +20,7 @@ describe("loadSharedConfigFiles", () => { configFile: mockConfigFilepath, credentialsFile: mockCredsFilepath, }; + const mockHomeDir = "/users/alias"; beforeEach(() => { (getConfigFilepath as jest.Mock).mockReturnValue(mockConfigFilepath); @@ -25,6 +28,7 @@ describe("loadSharedConfigFiles", () => { (parseIni as jest.Mock).mockImplementation((args) => args); (getConfigData as jest.Mock).mockImplementation((args) => args); (slurpFile as jest.Mock).mockImplementation((path) => Promise.resolve(path)); + (getHomeDir as jest.Mock).mockReturnValue(mockHomeDir); }); afterEach(() => { @@ -49,6 +53,20 @@ describe("loadSharedConfigFiles", () => { expect(getCredentialsFilepath).not.toHaveBeenCalled(); }); + it("expands homedir in configFile and credentialsFile from init if defined", async () => { + const sharedConfigFiles = await loadSharedConfigFiles({ + filepath: "~/path/credentials", + configFilepath: "~/path/config", + }); + expect(sharedConfigFiles).toStrictEqual({ + configFile: "/users/alias/path/config", + credentialsFile: "/users/alias/path/credentials", + }); + expect(getHomeDir).toHaveBeenCalled(); + expect(getConfigFilepath).not.toHaveBeenCalled(); + expect(getCredentialsFilepath).not.toHaveBeenCalled(); + }); + describe("swallows error and returns empty configuration", () => { it("when readFile throws error", async () => { (slurpFile as jest.Mock).mockRejectedValue("error"); diff --git a/packages/shared-ini-file-loader/src/loadSharedConfigFiles.ts b/packages/shared-ini-file-loader/src/loadSharedConfigFiles.ts index 9bf6336e877..21255090def 100644 --- a/packages/shared-ini-file-loader/src/loadSharedConfigFiles.ts +++ b/packages/shared-ini-file-loader/src/loadSharedConfigFiles.ts @@ -1,8 +1,10 @@ import { Logger, SharedConfigFiles } from "@smithy/types"; +import { join } from "path"; import { getConfigData } from "./getConfigData"; import { getConfigFilepath } from "./getConfigFilepath"; import { getCredentialsFilepath } from "./getCredentialsFilepath"; +import { getHomeDir } from "./getHomeDir"; import { parseIni } from "./parseIni"; import { slurpFile } from "./slurpFile"; @@ -39,15 +41,27 @@ export const CONFIG_PREFIX_SEPARATOR = "."; export const loadSharedConfigFiles = async (init: SharedConfigInit = {}): Promise => { const { filepath = getCredentialsFilepath(), configFilepath = getConfigFilepath() } = init; + const homeDir = getHomeDir(); + const relativeHomeDirPrefix = "~/"; + + let resolvedFilepath = filepath; + if (filepath.startsWith(relativeHomeDirPrefix)) { + resolvedFilepath = join(homeDir, filepath.slice(2)); + } + + let resolvedConfigFilepath = configFilepath; + if (configFilepath.startsWith(relativeHomeDirPrefix)) { + resolvedConfigFilepath = join(homeDir, configFilepath.slice(2)); + } const parsedFiles = await Promise.all([ - slurpFile(configFilepath, { + slurpFile(resolvedConfigFilepath, { ignoreCache: init.ignoreCache, }) .then(parseIni) .then(getConfigData) .catch(swallowError), - slurpFile(filepath, { + slurpFile(resolvedFilepath, { ignoreCache: init.ignoreCache, }) .then(parseIni) diff --git a/packages/signature-v4/CHANGELOG.md b/packages/signature-v4/CHANGELOG.md index 35f63cd4b92..e18f343af3d 100644 --- a/packages/signature-v4/CHANGELOG.md +++ b/packages/signature-v4/CHANGELOG.md @@ -1,5 +1,22 @@ # Change Log +## 4.1.0 + +### Minor Changes + +- 86862ea: switch to static HttpRequest clone method + +### Patch Changes + +- Updated dependencies [86862ea] + - @smithy/protocol-http@4.1.0 + +## 4.0.0 + +### Major Changes + +- ae8bf5c: Make sha256 required parameter for SigV4 constructor + ## 3.1.2 ### Patch Changes diff --git a/packages/signature-v4/package.json b/packages/signature-v4/package.json index a2545d8f384..e839764b42d 100644 --- a/packages/signature-v4/package.json +++ b/packages/signature-v4/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/signature-v4", - "version": "3.1.2", + "version": "4.1.0", "description": "A standalone implementation of the AWS Signature V4 request signing algorithm", "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -25,6 +25,7 @@ "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "workspace:^", + "@smithy/protocol-http": "workspace:^", "@smithy/types": "workspace:^", "@smithy/util-hex-encoding": "workspace:^", "@smithy/util-middleware": "workspace:^", @@ -34,7 +35,6 @@ }, "devDependencies": { "@aws-crypto/sha256-js": "5.2.0", - "@smithy/protocol-http": "workspace:^", "concurrently": "7.0.0", "downlevel-dts": "0.10.1", "rimraf": "3.0.2", diff --git a/packages/signature-v4/src/SignatureV4.spec.ts b/packages/signature-v4/src/SignatureV4.spec.ts index 7679df3577e..71c4e54e676 100644 --- a/packages/signature-v4/src/SignatureV4.spec.ts +++ b/packages/signature-v4/src/SignatureV4.spec.ts @@ -410,7 +410,7 @@ describe("SignatureV4", () => { }); it("should sign requests without host header", async () => { - const request = minimalRequest.clone(); + const request = HttpRequest.clone(minimalRequest); delete request.headers[HOST_HEADER]; const { headers } = await signer.sign(request, { signingDate: new Date("2000-01-01T00:00:00.000Z"), diff --git a/packages/signature-v4/src/cloneRequest.spec.ts b/packages/signature-v4/src/cloneRequest.spec.ts deleted file mode 100644 index 5abe71f54c1..00000000000 --- a/packages/signature-v4/src/cloneRequest.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { HttpRequest, QueryParameterBag } from "@smithy/types"; - -import { cloneRequest } from "./cloneRequest"; - -describe("cloneRequest", () => { - const request: HttpRequest = Object.freeze({ - method: "GET", - protocol: "https:", - hostname: "foo.us-west-2.amazonaws.com", - path: "/", - headers: Object.freeze({ - foo: "bar", - compound: "value 1, value 2", - }), - query: Object.freeze({ - fizz: "buzz", - snap: ["crackle", "pop"], - }), - }); - - it("should return an object matching the provided request", () => { - expect(cloneRequest(request)).toEqual(request); - }); - - it("should return an object that with a different identity", () => { - expect(cloneRequest(request)).not.toBe(request); - }); - - it("should should deep-copy the headers", () => { - const clone = cloneRequest(request); - - delete clone.headers.compound; - expect(Object.keys(request.headers)).toEqual(["foo", "compound"]); - expect(Object.keys(clone.headers)).toEqual(["foo"]); - }); - - it("should should deep-copy the query", () => { - const clone = cloneRequest(request); - - const { snap } = clone.query as QueryParameterBag; - (snap as Array).shift(); - - expect((request.query as QueryParameterBag).snap).toEqual(["crackle", "pop"]); - expect((clone.query as QueryParameterBag).snap).toEqual(["pop"]); - }); - - it("should not copy the body", () => { - const body = new Uint8Array(16); - const req = { ...request, body }; - const clone = cloneRequest(req); - - expect(clone.body).toBe(req.body); - }); - - it("should handle requests without defined query objects", () => { - expect(cloneRequest({ ...request, query: void 0 }).query).not.toBeDefined(); - }); -}); diff --git a/packages/signature-v4/src/cloneRequest.ts b/packages/signature-v4/src/cloneRequest.ts deleted file mode 100644 index d128288007a..00000000000 --- a/packages/signature-v4/src/cloneRequest.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { HttpRequest, QueryParameterBag } from "@smithy/types"; - -/** - * @internal - */ -export const cloneRequest = ({ headers, query, ...rest }: HttpRequest): HttpRequest => ({ - ...rest, - headers: { ...headers }, - query: query ? cloneQuery(query) : undefined, -}); - -export const cloneQuery = (query: QueryParameterBag): QueryParameterBag => - Object.keys(query).reduce((carry: QueryParameterBag, paramName: string) => { - const param = query[paramName]; - return { - ...carry, - [paramName]: Array.isArray(param) ? [...param] : param, - }; - }, {}); diff --git a/packages/signature-v4/src/moveHeadersToQuery.ts b/packages/signature-v4/src/moveHeadersToQuery.ts index 3fb34e2652a..c858bace0fa 100644 --- a/packages/signature-v4/src/moveHeadersToQuery.ts +++ b/packages/signature-v4/src/moveHeadersToQuery.ts @@ -1,16 +1,14 @@ -import { HttpRequest, QueryParameterBag } from "@smithy/types"; - -import { cloneRequest } from "./cloneRequest"; +import { HttpRequest } from "@smithy/protocol-http"; +import type { HttpRequest as IHttpRequest, QueryParameterBag } from "@smithy/types"; /** * @private */ export const moveHeadersToQuery = ( - request: HttpRequest, + request: IHttpRequest, options: { unhoistableHeaders?: Set } = {} -): HttpRequest & { query: QueryParameterBag } => { - const { headers, query = {} as QueryParameterBag } = - typeof (request as any).clone === "function" ? (request as any).clone() : cloneRequest(request); +): IHttpRequest & { query: QueryParameterBag } => { + const { headers, query = {} as QueryParameterBag } = HttpRequest.clone(request); for (const name of Object.keys(headers)) { const lname = name.toLowerCase(); if (lname.slice(0, 6) === "x-amz-" && !options.unhoistableHeaders?.has(lname)) { diff --git a/packages/signature-v4/src/prepareRequest.ts b/packages/signature-v4/src/prepareRequest.ts index 2b61ef842f2..35056646a2d 100644 --- a/packages/signature-v4/src/prepareRequest.ts +++ b/packages/signature-v4/src/prepareRequest.ts @@ -1,14 +1,14 @@ -import { HttpRequest } from "@smithy/types"; +import { HttpRequest } from "@smithy/protocol-http"; +import type { HttpRequest as IHttpRequest } from "@smithy/types"; -import { cloneRequest } from "./cloneRequest"; import { GENERATED_HEADERS } from "./constants"; /** * @private */ -export const prepareRequest = (request: HttpRequest): HttpRequest => { +export const prepareRequest = (request: IHttpRequest): IHttpRequest => { // Create a clone of the request object that does not clone the body - request = typeof (request as any).clone === "function" ? (request as any).clone() : cloneRequest(request); + request = HttpRequest.clone(request); for (const headerName of Object.keys(request.headers)) { if (GENERATED_HEADERS.indexOf(headerName.toLowerCase()) > -1) { diff --git a/packages/smithy-client/CHANGELOG.md b/packages/smithy-client/CHANGELOG.md index ce557ac4d09..a93039a32b2 100644 --- a/packages/smithy-client/CHANGELOG.md +++ b/packages/smithy-client/CHANGELOG.md @@ -1,5 +1,55 @@ # Change Log +## 3.1.12 + +### Patch Changes + +- 670553a: add command instance ref to smithy context + +## 3.1.11 + +### Patch Changes + +- @smithy/util-stream@3.1.3 + +## 3.1.10 + +### Patch Changes + +- Updated dependencies [4a40961] +- Updated dependencies [86862ea] + - @smithy/middleware-endpoint@3.1.0 + - @smithy/protocol-http@4.1.0 + - @smithy/util-stream@3.1.2 + +## 3.1.9 + +### Patch Changes + +- Updated dependencies [1cfe243] + - @smithy/util-stream@3.1.1 + +## 3.1.8 + +### Patch Changes + +- Updated dependencies [796567d] +- Updated dependencies [7cd258f] + - @smithy/protocol-http@4.0.4 + - @smithy/util-stream@3.1.0 + +## 3.1.7 + +### Patch Changes + +- @smithy/middleware-endpoint@3.0.5 + +## 3.1.6 + +### Patch Changes + +- @smithy/util-stream@3.0.6 + ## 3.1.5 ### Patch Changes diff --git a/packages/smithy-client/package.json b/packages/smithy-client/package.json index 7f7c52fd265..789ead59409 100644 --- a/packages/smithy-client/package.json +++ b/packages/smithy-client/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/smithy-client", - "version": "3.1.5", + "version": "3.1.12", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline smithy-client", diff --git a/packages/smithy-client/src/command.ts b/packages/smithy-client/src/command.ts index 290972b30c2..b100c8233e1 100644 --- a/packages/smithy-client/src/command.ts +++ b/packages/smithy-client/src/command.ts @@ -82,6 +82,7 @@ export abstract class Command< inputFilterSensitiveLog, outputFilterSensitiveLog, [SMITHY_CONTEXT_KEY]: { + commandInstance: this, ...smithyContext, }, ...additionalContext, diff --git a/packages/util-defaults-mode-browser/CHANGELOG.md b/packages/util-defaults-mode-browser/CHANGELOG.md index 4aa724e67a4..a47c8c369cf 100644 --- a/packages/util-defaults-mode-browser/CHANGELOG.md +++ b/packages/util-defaults-mode-browser/CHANGELOG.md @@ -1,5 +1,48 @@ # Change Log +## 3.0.14 + +### Patch Changes + +- Updated dependencies [670553a] + - @smithy/smithy-client@3.1.12 + +## 3.0.13 + +### Patch Changes + +- @smithy/smithy-client@3.1.11 + +## 3.0.12 + +### Patch Changes + +- @smithy/smithy-client@3.1.10 + +## 3.0.11 + +### Patch Changes + +- @smithy/smithy-client@3.1.9 + +## 3.0.10 + +### Patch Changes + +- @smithy/smithy-client@3.1.8 + +## 3.0.9 + +### Patch Changes + +- @smithy/smithy-client@3.1.7 + +## 3.0.8 + +### Patch Changes + +- @smithy/smithy-client@3.1.6 + ## 3.0.7 ### Patch Changes diff --git a/packages/util-defaults-mode-browser/package.json b/packages/util-defaults-mode-browser/package.json index 0a97dcb8e3c..6b7eacc36ef 100644 --- a/packages/util-defaults-mode-browser/package.json +++ b/packages/util-defaults-mode-browser/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/util-defaults-mode-browser", - "version": "3.0.7", + "version": "3.0.14", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline util-defaults-mode-browser", diff --git a/packages/util-defaults-mode-node/CHANGELOG.md b/packages/util-defaults-mode-node/CHANGELOG.md index 9c181c9d69c..f47019b6d8e 100644 --- a/packages/util-defaults-mode-node/CHANGELOG.md +++ b/packages/util-defaults-mode-node/CHANGELOG.md @@ -1,5 +1,53 @@ # Change Log +## 3.0.14 + +### Patch Changes + +- Updated dependencies [670553a] + - @smithy/smithy-client@3.1.12 + +## 3.0.13 + +### Patch Changes + +- @smithy/smithy-client@3.1.11 + +## 3.0.12 + +### Patch Changes + +- Updated dependencies [3d72b04] + - @smithy/credential-provider-imds@3.2.0 + - @smithy/smithy-client@3.1.10 + +## 3.0.11 + +### Patch Changes + +- @smithy/smithy-client@3.1.9 + +## 3.0.10 + +### Patch Changes + +- @smithy/smithy-client@3.1.8 + +## 3.0.9 + +### Patch Changes + +- @smithy/node-config-provider@3.1.4 +- @smithy/smithy-client@3.1.7 +- @smithy/config-resolver@3.0.5 +- @smithy/credential-provider-imds@3.1.4 + +## 3.0.8 + +### Patch Changes + +- @smithy/smithy-client@3.1.6 + ## 3.0.7 ### Patch Changes diff --git a/packages/util-defaults-mode-node/package.json b/packages/util-defaults-mode-node/package.json index 46532e0f8d7..4f3b1db4807 100644 --- a/packages/util-defaults-mode-node/package.json +++ b/packages/util-defaults-mode-node/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/util-defaults-mode-node", - "version": "3.0.7", + "version": "3.0.14", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline util-defaults-mode-node", diff --git a/packages/util-endpoints/CHANGELOG.md b/packages/util-endpoints/CHANGELOG.md index 9632c15adb6..6b64bbcff5f 100644 --- a/packages/util-endpoints/CHANGELOG.md +++ b/packages/util-endpoints/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 2.0.5 + +### Patch Changes + +- @smithy/node-config-provider@3.1.4 + ## 2.0.4 ### Patch Changes diff --git a/packages/util-endpoints/package.json b/packages/util-endpoints/package.json index cad68f39902..eeb245270ef 100644 --- a/packages/util-endpoints/package.json +++ b/packages/util-endpoints/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/util-endpoints", - "version": "2.0.4", + "version": "2.0.5", "description": "Utilities to help with endpoint resolution.", "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", diff --git a/packages/util-stream-browser/CHANGELOG.md b/packages/util-stream-browser/CHANGELOG.md index 57e499a4a5f..3f2cc5bbe2e 100644 --- a/packages/util-stream-browser/CHANGELOG.md +++ b/packages/util-stream-browser/CHANGELOG.md @@ -1,5 +1,31 @@ # Change Log +## 2.2.10 + +### Patch Changes + +- Updated dependencies [3ea4789] + - @smithy/fetch-http-handler@3.2.4 + +## 2.2.9 + +### Patch Changes + +- @smithy/fetch-http-handler@3.2.3 + +## 2.2.8 + +### Patch Changes + +- @smithy/fetch-http-handler@3.2.2 + +## 2.2.7 + +### Patch Changes + +- Updated dependencies [f31cc5f] + - @smithy/fetch-http-handler@3.2.1 + ## 2.2.6 ### Patch Changes diff --git a/packages/util-stream-browser/package.json b/packages/util-stream-browser/package.json index 32ad5dd03ff..d5a6d977f53 100644 --- a/packages/util-stream-browser/package.json +++ b/packages/util-stream-browser/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/util-stream-browser", - "version": "2.2.6", + "version": "2.2.10", "scripts": { "build": "concurrently 'yarn:build:es' 'yarn:build:types'", "build:es": "yarn g:tsc -p tsconfig.es.json", diff --git a/packages/util-stream-node/CHANGELOG.md b/packages/util-stream-node/CHANGELOG.md index 79c7be9b2f7..f5769141ebd 100644 --- a/packages/util-stream-node/CHANGELOG.md +++ b/packages/util-stream-node/CHANGELOG.md @@ -1,5 +1,24 @@ # Change Log +## 3.0.6 + +### Patch Changes + +- @smithy/node-http-handler@3.1.4 + +## 3.0.5 + +### Patch Changes + +- @smithy/node-http-handler@3.1.3 + +## 3.0.4 + +### Patch Changes + +- Updated dependencies [f31cc5f] + - @smithy/node-http-handler@3.1.2 + ## 3.0.3 ### Patch Changes diff --git a/packages/util-stream-node/package.json b/packages/util-stream-node/package.json index 8751638da06..35f8b55367a 100644 --- a/packages/util-stream-node/package.json +++ b/packages/util-stream-node/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/util-stream-node", - "version": "3.0.3", + "version": "3.0.6", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline util-stream-node", diff --git a/packages/util-stream/CHANGELOG.md b/packages/util-stream/CHANGELOG.md index 297a2816e20..3dc2ae32916 100644 --- a/packages/util-stream/CHANGELOG.md +++ b/packages/util-stream/CHANGELOG.md @@ -1,5 +1,44 @@ # Change Log +## 3.1.3 + +### Patch Changes + +- Updated dependencies [3ea4789] + - @smithy/fetch-http-handler@3.2.4 + +## 3.1.2 + +### Patch Changes + +- @smithy/fetch-http-handler@3.2.3 +- @smithy/node-http-handler@3.1.4 + +## 3.1.1 + +### Patch Changes + +- 1cfe243: avoid compilation of global ReadableStream with type parameter + +## 3.1.0 + +### Minor Changes + +- 7cd258f: add splitStream and headStream utilities + +### Patch Changes + +- @smithy/fetch-http-handler@3.2.2 +- @smithy/node-http-handler@3.1.3 + +## 3.0.6 + +### Patch Changes + +- Updated dependencies [f31cc5f] + - @smithy/fetch-http-handler@3.2.1 + - @smithy/node-http-handler@3.1.2 + ## 3.0.5 ### Patch Changes diff --git a/packages/util-stream/package.json b/packages/util-stream/package.json index b7f9c30e44c..6182ac21ec3 100644 --- a/packages/util-stream/package.json +++ b/packages/util-stream/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/util-stream", - "version": "3.0.5", + "version": "3.1.3", "scripts": { "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types && yarn build:types:downlevel'", "build:cjs": "node ../../scripts/inline util-stream", @@ -56,13 +56,19 @@ ], "browser": { "./dist-es/getAwsChunkedEncodingStream": "./dist-es/getAwsChunkedEncodingStream.browser", - "./dist-es/sdk-stream-mixin": "./dist-es/sdk-stream-mixin.browser" + "./dist-es/headStream": "./dist-es/headStream.browser", + "./dist-es/sdk-stream-mixin": "./dist-es/sdk-stream-mixin.browser", + "./dist-es/splitStream": "./dist-es/splitStream.browser" }, "react-native": { "./dist-es/getAwsChunkedEncodingStream": "./dist-es/getAwsChunkedEncodingStream.browser", "./dist-es/sdk-stream-mixin": "./dist-es/sdk-stream-mixin.browser", + "./dist-es/headStream": "./dist-es/headStream.browser", + "./dist-es/splitStream": "./dist-es/splitStream.browser", "./dist-cjs/getAwsChunkedEncodingStream": "./dist-cjs/getAwsChunkedEncodingStream.browser", - "./dist-cjs/sdk-stream-mixin": "./dist-cjs/sdk-stream-mixin.browser" + "./dist-cjs/sdk-stream-mixin": "./dist-cjs/sdk-stream-mixin.browser", + "./dist-cjs/headStream": "./dist-cjs/headStream.browser", + "./dist-cjs/splitStream": "./dist-cjs/splitStream.browser" }, "homepage": "https://github.com/awslabs/smithy-typescript/tree/main/packages/util-stream", "repository": { diff --git a/packages/util-stream/src/headStream.browser.ts b/packages/util-stream/src/headStream.browser.ts new file mode 100644 index 00000000000..afdc0995f00 --- /dev/null +++ b/packages/util-stream/src/headStream.browser.ts @@ -0,0 +1,39 @@ +/** + * @internal + * @param stream + * @param bytes - read head bytes from the stream and discard the rest of it. + * + * Caution: the input stream must be destroyed separately, this function does not do so. + */ +export async function headStream(stream: ReadableStream, bytes: number): Promise { + let byteLengthCounter = 0; + const chunks = []; + const reader = stream.getReader(); + let isDone = false; + + while (!isDone) { + const { done, value } = await reader.read(); + if (value) { + chunks.push(value); + byteLengthCounter += value?.byteLength ?? 0; + } + if (byteLengthCounter >= bytes) { + break; + } + isDone = done; + } + reader.releaseLock(); + + const collected = new Uint8Array(Math.min(bytes, byteLengthCounter)); + let offset = 0; + for (const chunk of chunks) { + if (chunk.byteLength > collected.byteLength - offset) { + collected.set(chunk.subarray(0, collected.byteLength - offset), offset); + break; + } else { + collected.set(chunk, offset); + } + offset += chunk.length; + } + return collected; +} diff --git a/packages/util-stream/src/headStream.spec.ts b/packages/util-stream/src/headStream.spec.ts new file mode 100644 index 00000000000..18294f9bf7a --- /dev/null +++ b/packages/util-stream/src/headStream.spec.ts @@ -0,0 +1,108 @@ +import { Readable } from "stream"; + +import { headStream } from "./headStream"; +import { headStream as headWebStream } from "./headStream.browser"; +import { splitStream } from "./splitStream"; +import { splitStream as splitWebStream } from "./splitStream.browser"; + +const CHUNK_SIZE = 4; +const a32 = "abcd".repeat(32_000 / CHUNK_SIZE); +const a16 = "abcd".repeat(16_000 / CHUNK_SIZE); +const a8 = "abcd".repeat(8); +const a4 = "abcd".repeat(4); +const a2 = "abcd".repeat(2); +const a1 = "abcd".repeat(1); + +describe(headStream.name, () => { + it("should collect the head of a Node.js stream", async () => { + const data = Buffer.from(a32); + const myStream = Readable.from(data); + + const head = await headStream(myStream, 16_000); + + expect(Buffer.from(head).toString()).toEqual(a16); + }); + + it("should collect the head of a web stream", async () => { + if (typeof ReadableStream !== "undefined") { + const buffer = Buffer.from(a32); + const data = Array.from(new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)); + + const myStream = new ReadableStream({ + start(controller) { + for (const inputChunk of data) { + controller.enqueue(new Uint8Array([inputChunk])); + } + controller.close(); + }, + }); + + const head = await headWebStream(myStream, 16_000); + expect(Buffer.from(head).toString()).toEqual(a16); + } + }); +}); + +describe("splitStream and headStream integration", () => { + it("should split and head streams for Node.js", async () => { + const data = Buffer.from(a32); + const myStream = Readable.from(data); + + const [a, _1] = await splitStream(myStream); + const [b, _2] = await splitStream(_1); + const [c, _3] = await splitStream(_2); + const [d, _4] = await splitStream(_3); + const [e, f] = await splitStream(_4); + + const byteArr1 = await headStream(a, Infinity); + const byteArr2 = await headStream(b, 16_000); + const byteArr3 = await headStream(c, 8 * CHUNK_SIZE); + const byteArr4 = await headStream(d, 4 * CHUNK_SIZE); + const byteArr5 = await headStream(e, 2 * CHUNK_SIZE); + const byteArr6 = await headStream(f, CHUNK_SIZE); + + await Promise.all([a, b, c, d, e, f].map((stream) => stream.destroy())); + + expect(Buffer.from(byteArr1).toString()).toEqual(a32); + expect(Buffer.from(byteArr2).toString()).toEqual(a16); + expect(Buffer.from(byteArr3).toString()).toEqual(a8); + expect(Buffer.from(byteArr4).toString()).toEqual(a4); + expect(Buffer.from(byteArr5).toString()).toEqual(a2); + expect(Buffer.from(byteArr6).toString()).toEqual(a1); + }); + + it("should split and head streams for web streams API", async () => { + if (typeof ReadableStream !== "undefined") { + const buffer = Buffer.from(a8); + const data = Array.from(new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)); + + const myStream = new ReadableStream({ + start(controller) { + for (let i = 0; i < data.length; i += CHUNK_SIZE) { + controller.enqueue(new Uint8Array(data.slice(i, i + CHUNK_SIZE))); + } + controller.close(); + }, + }); + + const [a, _1] = await splitWebStream(myStream); + const [b, _2] = await splitWebStream(_1); + const [c, _3] = await splitWebStream(_2); + const [d, e] = await splitWebStream(_3); + + const byteArr1 = await headWebStream(a, Infinity); + const byteArr2 = await headWebStream(b, 8 * CHUNK_SIZE); + const byteArr3 = await headWebStream(c, 4 * CHUNK_SIZE); + const byteArr4 = await headWebStream(d, 2 * CHUNK_SIZE); + const byteArr5 = await headWebStream(e, CHUNK_SIZE); + + await Promise.all([a, b, c, d, e].map((stream) => stream.cancel())); + + expect(Buffer.from(byteArr1).toString()).toEqual(a8); + expect(Buffer.from(byteArr2).toString()).toEqual(a8); + expect(Buffer.from(byteArr3).toString()).toEqual(a4); + expect(Buffer.from(byteArr4).toString()).toEqual(a2); + expect(Buffer.from(byteArr5).toString()).toEqual(a1); + } + }); +}); diff --git a/packages/util-stream/src/headStream.ts b/packages/util-stream/src/headStream.ts new file mode 100644 index 00000000000..0fe80f1e602 --- /dev/null +++ b/packages/util-stream/src/headStream.ts @@ -0,0 +1,49 @@ +import { Readable, Writable } from "stream"; + +import { headStream as headWebStream } from "./headStream.browser"; +import { isReadableStream } from "./stream-type-check"; + +/** + * @internal + * @param stream + * @param bytes - read head bytes from the stream and discard the rest of it. + * + * Caution: the input stream must be destroyed separately, this function does not do so. + */ +export const headStream = (stream: Readable | ReadableStream, bytes: number): Promise => { + if (isReadableStream(stream)) { + return headWebStream(stream, bytes); + } + return new Promise((resolve, reject) => { + const collector = new Collector(); + collector.limit = bytes; + stream.pipe(collector); + stream.on("error", (err) => { + collector.end(); + reject(err); + }); + collector.on("error", reject); + collector.on("finish", function (this: Collector) { + const bytes = new Uint8Array(Buffer.concat(this.buffers)); + resolve(bytes); + }); + }); +}; + +class Collector extends Writable { + public readonly buffers: Buffer[] = []; + public limit = Infinity; + private bytesBuffered = 0; + + _write(chunk: Buffer, encoding: string, callback: (err?: Error) => void) { + this.buffers.push(chunk); + this.bytesBuffered += chunk.byteLength ?? 0; + if (this.bytesBuffered >= this.limit) { + const excess = this.bytesBuffered - this.limit; + const tailBuffer = this.buffers[this.buffers.length - 1]; + this.buffers[this.buffers.length - 1] = tailBuffer.subarray(0, tailBuffer.byteLength - excess); + this.emit("finish"); + } + callback(); + } +} diff --git a/packages/util-stream/src/index.ts b/packages/util-stream/src/index.ts index 47bcb14b718..305896d2fa0 100644 --- a/packages/util-stream/src/index.ts +++ b/packages/util-stream/src/index.ts @@ -1,3 +1,6 @@ export * from "./blob/Uint8ArrayBlobAdapter"; export * from "./getAwsChunkedEncodingStream"; export * from "./sdk-stream-mixin"; +export * from "./splitStream"; +export * from "./headStream"; +export * from "./stream-type-check"; diff --git a/packages/util-stream/src/sdk-stream-mixin.browser.ts b/packages/util-stream/src/sdk-stream-mixin.browser.ts index 5fd86aabc05..f91252527c0 100644 --- a/packages/util-stream/src/sdk-stream-mixin.browser.ts +++ b/packages/util-stream/src/sdk-stream-mixin.browser.ts @@ -4,6 +4,8 @@ import { toBase64 } from "@smithy/util-base64"; import { toHex } from "@smithy/util-hex-encoding"; import { toUtf8 } from "@smithy/util-utf8"; +import { isReadableStream } from "./stream-type-check"; + const ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED = "The stream has already been transformed."; /** @@ -12,7 +14,7 @@ const ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED = "The stream has already been transfo * @internal */ export const sdkStreamMixin = (stream: unknown): SdkStream => { - if (!isBlobInstance(stream) && !isReadableStreamInstance(stream)) { + if (!isBlobInstance(stream) && !isReadableStream(stream)) { //@ts-ignore const name = stream?.__proto__?.constructor?.name || stream; throw new Error(`Unexpected stream implementation, expect Blob or ReadableStream, got ${name}`); @@ -64,7 +66,7 @@ export const sdkStreamMixin = (stream: unknown): SdkStream typeof Blob === "function" && stream instanceof Blob; - -const isReadableStreamInstance = (stream: unknown): stream is ReadableStream => - typeof ReadableStream === "function" && stream instanceof ReadableStream; diff --git a/packages/util-stream/src/splitStream.browser.ts b/packages/util-stream/src/splitStream.browser.ts new file mode 100644 index 00000000000..16314858512 --- /dev/null +++ b/packages/util-stream/src/splitStream.browser.ts @@ -0,0 +1,11 @@ +/** + * @param stream + * @returns stream split into two identical streams. + */ +export async function splitStream(stream: ReadableStream | Blob): Promise<[ReadableStream, ReadableStream]> { + if (typeof (stream as Blob).stream === "function") { + stream = (stream as Blob).stream(); + } + const readableStream = stream as ReadableStream; + return readableStream.tee(); +} diff --git a/packages/util-stream/src/splitStream.spec.ts b/packages/util-stream/src/splitStream.spec.ts new file mode 100644 index 00000000000..2c1ecf90d0f --- /dev/null +++ b/packages/util-stream/src/splitStream.spec.ts @@ -0,0 +1,43 @@ +import { streamCollector as webStreamCollector } from "@smithy/fetch-http-handler"; +import { streamCollector } from "@smithy/node-http-handler"; +import { Readable } from "stream"; + +import { splitStream } from "./splitStream"; +import { splitStream as splitWebStream } from "./splitStream.browser"; + +describe(splitStream.name, () => { + it("should split a node:Readable stream", async () => { + const data = Buffer.from("abcd"); + + const myStream = Readable.from(data); + const [a, b] = await splitStream(myStream); + + const buffer1 = await streamCollector(a); + const buffer2 = await streamCollector(b); + + expect(buffer1).toEqual(new Uint8Array([97, 98, 99, 100])); + expect(buffer1).toEqual(buffer2); + }); + it("should split a web:ReadableStream stream", async () => { + if (typeof ReadableStream !== "undefined") { + const inputChunks = [97, 98, 99, 100]; + + const myStream = new ReadableStream({ + start(controller) { + for (const inputChunk of inputChunks) { + controller.enqueue(new Uint8Array([inputChunk])); + } + controller.close(); + }, + }); + + const [a, b] = await splitWebStream(myStream); + + const bytes1 = await webStreamCollector(a); + const bytes2 = await webStreamCollector(b); + + expect(bytes1).toEqual(new Uint8Array([97, 98, 99, 100])); + expect(bytes1).toEqual(bytes2); + } + }); +}); diff --git a/packages/util-stream/src/splitStream.ts b/packages/util-stream/src/splitStream.ts new file mode 100644 index 00000000000..c9ff165181e --- /dev/null +++ b/packages/util-stream/src/splitStream.ts @@ -0,0 +1,24 @@ +import type { Readable } from "stream"; +import { PassThrough } from "stream"; + +import { splitStream as splitWebStream } from "./splitStream.browser"; +import { isReadableStream } from "./stream-type-check"; + +/** + * @param stream + * @returns stream split into two identical streams. + */ +export async function splitStream(stream: Readable): Promise<[Readable, Readable]>; +export async function splitStream(stream: ReadableStream): Promise<[ReadableStream, ReadableStream]>; +export async function splitStream( + stream: Readable | ReadableStream +): Promise<[Readable | ReadableStream, Readable | ReadableStream]> { + if (isReadableStream(stream)) { + return splitWebStream(stream); + } + const stream1 = new PassThrough(); + const stream2 = new PassThrough(); + stream.pipe(stream1); + stream.pipe(stream2); + return [stream1, stream2]; +} diff --git a/packages/util-stream/src/stream-type-check.ts b/packages/util-stream/src/stream-type-check.ts new file mode 100644 index 00000000000..4bc26837c33 --- /dev/null +++ b/packages/util-stream/src/stream-type-check.ts @@ -0,0 +1,14 @@ +/** + * @internal + * Alias prevents compiler from turning + * ReadableStream into ReadableStream, which is incompatible + * with the NodeJS.ReadableStream global type. + */ +type ReadableStreamType = ReadableStream; + +/** + * @internal + */ +export const isReadableStream = (stream: unknown): stream is ReadableStreamType => + typeof ReadableStream === "function" && + (stream?.constructor?.name === ReadableStream.name || stream instanceof ReadableStream); diff --git a/private/util-test/CHANGELOG.md b/private/util-test/CHANGELOG.md index aff8eb0b81a..458f865e33c 100644 --- a/private/util-test/CHANGELOG.md +++ b/private/util-test/CHANGELOG.md @@ -1,5 +1,19 @@ # @smithy/util-test +## 0.2.6 + +### Patch Changes + +- Updated dependencies [86862ea] + - @smithy/protocol-http@4.1.0 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies [796567d] + - @smithy/protocol-http@4.0.4 + ## 0.2.4 ### Patch Changes diff --git a/private/util-test/package.json b/private/util-test/package.json index 7220f0ff83e..bf2ea797f3b 100644 --- a/private/util-test/package.json +++ b/private/util-test/package.json @@ -1,6 +1,6 @@ { "name": "@smithy/util-test", - "version": "0.2.4", + "version": "0.2.6", "private": true, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", diff --git a/scripts/build-generated-test-packages.js b/scripts/build-generated-test-packages.js index 65c378b8bb7..4797a7aa884 100644 --- a/scripts/build-generated-test-packages.js +++ b/scripts/build-generated-test-packages.js @@ -9,59 +9,39 @@ const { spawnProcess } = require("./utils/spawn-process"); const root = path.join(__dirname, ".."); -const testProjectDir = path.join( - root, - "smithy-typescript-codegen-test", -); +const testProjectDir = path.join(root, "smithy-typescript-codegen-test"); -const codegenTestDir = path.join( - testProjectDir, - "build", - "smithyprojections", - "smithy-typescript-codegen-test", -); +const codegenTestDir = path.join(testProjectDir, "build", "smithyprojections", "smithy-typescript-codegen-test"); -const weatherClientDir = path.join( - codegenTestDir, - "source", - "typescript-client-codegen" -); +const weatherClientDir = path.join(codegenTestDir, "source", "typescript-client-codegen"); const releasedClientDir = path.join( - testProjectDir, - "released-version-test", - "build", - "smithyprojections", - "released-version-test", - "source", - "typescript-codegen" + testProjectDir, + "released-version-test", + "build", + "smithyprojections", + "released-version-test", + "source", + "typescript-codegen" ); -// TODO(experimentalIdentityAndAuth): build generic client for integration tests -const weatherExperimentalIdentityAndAuthClientDir = path.join( - codegenTestDir, - "client-experimental-identity-and-auth", - "typescript-client-codegen" -); +// Build generic legacy auth client for integration tests +const weatherLegacyAuthClientDir = path.join(codegenTestDir, "client-legacy-auth", "typescript-client-codegen"); -const weatherSsdkDir = path.join( - codegenTestDir, - "ssdk-test", - "typescript-server-codegen" -) +const weatherSsdkDir = path.join(codegenTestDir, "ssdk-test", "typescript-server-codegen"); -// TODO(experimentalIdentityAndAuth): add `@httpApiKeyAuth` client for integration tests +// Build `@httpApiKeyAuth` client for integration tests const httpApiKeyAuthClientDir = path.join( - codegenTestDir, - "identity-and-auth-http-api-key-auth", - "typescript-client-codegen" + codegenTestDir, + "identity-and-auth-http-api-key-auth", + "typescript-client-codegen" ); -// TODO(experimentalIdentityAndAuth): add `@httpBearerAuth` client for integration tests +// Build `@httpBearerAuth` client for integration tests const httpBearerAuthClientDir = path.join( - codegenTestDir, - "identity-and-auth-http-bearer-auth", - "typescript-client-codegen" + codegenTestDir, + "identity-and-auth-http-bearer-auth", + "typescript-client-codegen" ); const nodeModulesDir = path.join(root, "node_modules"); @@ -82,10 +62,14 @@ const buildAndCopyToNodeModules = async (packageName, codegenDir, nodeModulesDir await spawnProcess("rm", ["-rf", packageName], { cwd: nodeModulesDir }); await spawnProcess("mkdir", ["-p", packageName], { cwd: nodeModulesDir }); const targetPackageDir = path.join(nodeModulesDir, packageName); - await spawnProcess("tar", ["-xf", "package.tgz", "-C", targetPackageDir, "--strip-components", "1"], { cwd: codegenDir }); + await spawnProcess("tar", ["-xf", "package.tgz", "-C", targetPackageDir, "--strip-components", "1"], { + cwd: codegenDir, + }); } } catch (e) { - console.log(`Building and copying package \`${packageName}\` in \`${codegenDir}\` to \`${nodeModulesDir}\` failed:`) + console.log( + `Building and copying package \`${packageName}\` in \`${codegenDir}\` to \`${nodeModulesDir}\` failed:` + ); console.log(e); process.exit(1); } @@ -94,12 +78,17 @@ const buildAndCopyToNodeModules = async (packageName, codegenDir, nodeModulesDir (async () => { await buildAndCopyToNodeModules("weather", weatherClientDir, nodeModulesDir); await buildAndCopyToNodeModules("weather-ssdk", weatherSsdkDir, nodeModulesDir); - // TODO(experimentalIdentityAndAuth): build generic client for integration tests - await buildAndCopyToNodeModules("@smithy/weather-experimental-identity-and-auth", weatherExperimentalIdentityAndAuthClientDir, nodeModulesDir); - // TODO(experimentalIdentityAndAuth): add `@httpApiKeyAuth` client for integration tests - await buildAndCopyToNodeModules("@smithy/identity-and-auth-http-api-key-auth-service", httpApiKeyAuthClientDir, nodeModulesDir); - // TODO(experimentalIdentityAndAuth): add `@httpBearerAuth` client for integration tests - await buildAndCopyToNodeModules("@smithy/identity-and-auth-http-bearer-auth-service", httpBearerAuthClientDir, nodeModulesDir); - // Test released version of smithy-typescript codegenerators, but - await buildAndCopyToNodeModules("released", releasedClientDir, undefined); + await buildAndCopyToNodeModules("@smithy/weather-legacy-auth", weatherLegacyAuthClientDir, nodeModulesDir); + await buildAndCopyToNodeModules( + "@smithy/identity-and-auth-http-api-key-auth-service", + httpApiKeyAuthClientDir, + nodeModulesDir + ); + await buildAndCopyToNodeModules( + "@smithy/identity-and-auth-http-bearer-auth-service", + httpBearerAuthClientDir, + nodeModulesDir + ); + // TODO(released-version-test): Test released version of smithy-typescript codegenerators, but currently is not working + // await buildAndCopyToNodeModules("released", releasedClientDir, undefined); })(); diff --git a/smithy-typescript-codegen-test/example-weather-customizations/src/main/java/example/weather/SupportWeatherSigV4Auth.java b/smithy-typescript-codegen-test/example-weather-customizations/src/main/java/example/weather/SupportWeatherSigV4Auth.java new file mode 100644 index 00000000000..9384e44ba63 --- /dev/null +++ b/smithy-typescript-codegen-test/example-weather-customizations/src/main/java/example/weather/SupportWeatherSigV4Auth.java @@ -0,0 +1,133 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package example.weather; + +import java.util.Optional; +import java.util.function.Consumer; +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.codegen.core.SymbolReference; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.typescript.codegen.ApplicationProtocol; +import software.amazon.smithy.typescript.codegen.LanguageTarget; +import software.amazon.smithy.typescript.codegen.TypeScriptDependency; +import software.amazon.smithy.typescript.codegen.TypeScriptSettings; +import software.amazon.smithy.typescript.codegen.TypeScriptWriter; +import software.amazon.smithy.typescript.codegen.auth.http.ConfigField; +import software.amazon.smithy.typescript.codegen.auth.http.HttpAuthOptionProperty; +import software.amazon.smithy.typescript.codegen.auth.http.HttpAuthScheme; +import software.amazon.smithy.typescript.codegen.auth.http.HttpAuthSchemeParameter; +import software.amazon.smithy.typescript.codegen.auth.http.integration.HttpAuthTypeScriptIntegration; +import software.amazon.smithy.utils.SmithyInternalApi; + +@SmithyInternalApi +public final class SupportWeatherSigV4Auth implements HttpAuthTypeScriptIntegration { + static final Symbol AWS_CREDENTIAL_IDENTITY = Symbol.builder() + .name("AwsCredentialIdentity") + .namespace(TypeScriptDependency.SMITHY_TYPES.getPackageName(), "/") + .addDependency(TypeScriptDependency.SMITHY_TYPES) + .build(); + static final Symbol AWS_CREDENTIAL_IDENTITY_PROVIDER = Symbol.builder() + .name("AwsCredentialIdentityProvider") + .namespace(TypeScriptDependency.SMITHY_TYPES.getPackageName(), "/") + .addDependency(TypeScriptDependency.SMITHY_TYPES) + .build(); + static final ConfigField CREDENTIALS_CONFIG_FIELD = ConfigField.builder() + .name("credentials") + .type(ConfigField.Type.MAIN) + .docs(w -> w.write("The credentials used to sign requests.")) + .inputType(Symbol.builder() + .name("AwsCredentialIdentity | AwsCredentialIdentityProvider") + .addReference(AWS_CREDENTIAL_IDENTITY) + .addReference(AWS_CREDENTIAL_IDENTITY_PROVIDER) + .build()) + .resolvedType(Symbol.builder() + .name("AwsCredentialIdentityProvider") + .addReference(AWS_CREDENTIAL_IDENTITY) + .addReference(AWS_CREDENTIAL_IDENTITY_PROVIDER) + .build()) + .configFieldWriter(ConfigField::defaultMainConfigFieldWriter) + .build(); + private static final Consumer AWS_SIGV4_AUTH_SIGNER = w -> { + w.addDependency(TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH); + w.addImport("SigV4Signer", null, TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH); + w.write("new SigV4Signer()"); + }; + private static final SymbolReference PROVIDER = SymbolReference.builder() + .symbol(Symbol.builder() + .name("Provider") + .namespace(TypeScriptDependency.SMITHY_TYPES.getPackageName(), "/") + .addDependency(TypeScriptDependency.SMITHY_TYPES) + .build()) + .alias("__Provider") + .build(); + + @Override + public boolean matchesSettings(TypeScriptSettings settings) { + return !settings.useLegacyAuth(); + } + + @Override + public Optional getHttpAuthScheme() { + return Optional.of(HttpAuthScheme.builder() + .schemeId(ShapeId.from("aws.auth#sigv4")) + .applicationProtocol(ApplicationProtocol.createDefaultHttpApplicationProtocol()) + .putDefaultSigner(LanguageTarget.SHARED, AWS_SIGV4_AUTH_SIGNER) + .addConfigField(CREDENTIALS_CONFIG_FIELD) + .addConfigField(ConfigField.builder() + .name("region") + .type(ConfigField.Type.AUXILIARY) + .docs(w -> w.write("The AWS region to which this client will send requests.")) + .inputType(Symbol.builder() + .name("string | __Provider") + .addReference(PROVIDER) + .build()) + .resolvedType(Symbol.builder() + .name("__Provider") + .addReference(PROVIDER) + .build()) + .configFieldWriter(ConfigField::defaultAuxiliaryConfigFieldWriter) + .build()) + .addHttpAuthSchemeParameter(HttpAuthSchemeParameter.builder() + .name("region") + .type(w -> w.write("string")) + .source(w -> { + w.addDependency(TypeScriptDependency.UTIL_MIDDLEWARE); + w.addImport("normalizeProvider", null, TypeScriptDependency.UTIL_MIDDLEWARE); + w.openBlock("await normalizeProvider(config.region)() || (() => {", "})()", () -> { + w.write("throw new Error(\"expected `region` to be configured for `aws.auth#sigv4`\");"); + }); + }) + .build()) + .addHttpAuthOptionProperty(HttpAuthOptionProperty.builder() + .name("name") + .type(HttpAuthOptionProperty.Type.SIGNING) + .source(s -> w -> { + w.write("$S", s.trait().toNode().expectObjectNode().getMember("name")); + }) + .build()) + .addHttpAuthOptionProperty(HttpAuthOptionProperty.builder() + .name("region") + .type(HttpAuthOptionProperty.Type.SIGNING) + .source(t -> w -> { + w.write("authParameters.region"); + }) + .build()) + .propertiesExtractor(s -> w -> w + .write(""" + (config, context) => { + return { + /** + * @internal + */ + signingProperties: { + ...config, + ...context, + }, + }; + },""")) + .build()); + } +} diff --git a/smithy-typescript-codegen-test/example-weather-customizations/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration b/smithy-typescript-codegen-test/example-weather-customizations/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration index 93c3c677e46..17504f263bd 100644 --- a/smithy-typescript-codegen-test/example-weather-customizations/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration +++ b/smithy-typescript-codegen-test/example-weather-customizations/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration @@ -1 +1,2 @@ example.weather.ExampleWeatherCustomEndpointsRuntimeConfig +example.weather.SupportWeatherSigV4Auth diff --git a/smithy-typescript-codegen-test/model/weather/main.smithy b/smithy-typescript-codegen-test/model/weather/main.smithy index e8abc79201c..6446b1c9eb5 100644 --- a/smithy-typescript-codegen-test/model/weather/main.smithy +++ b/smithy-typescript-codegen-test/model/weather/main.smithy @@ -34,7 +34,7 @@ service Weather { GetCurrentTime // util-stream.integ.spec.ts Invoke - // experimentalIdentityAndAuth + // Identity and Auth OnlyHttpApiKeyAuth OnlyHttpApiKeyAuthOptional OnlyHttpBearerAuth diff --git a/smithy-typescript-codegen-test/released-version-test/build.gradle.kts b/smithy-typescript-codegen-test/released-version-test/build.gradle.kts index a46cf794210..38e874f58a0 100644 --- a/smithy-typescript-codegen-test/released-version-test/build.gradle.kts +++ b/smithy-typescript-codegen-test/released-version-test/build.gradle.kts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +// TODO(released-version-test): Test released version of smithy-typescript codegenerators, but currently is extremely flaky +/* plugins { java id("software.amazon.smithy.gradle.smithy-base") @@ -27,3 +29,4 @@ dependencies { } tasks["jar"].enabled = false +*/ \ No newline at end of file diff --git a/smithy-typescript-codegen-test/smithy-build.json b/smithy-typescript-codegen-test/smithy-build.json index a5251a5e386..704f8eb617b 100644 --- a/smithy-typescript-codegen-test/smithy-build.json +++ b/smithy-typescript-codegen-test/smithy-build.json @@ -29,7 +29,7 @@ } } }, - "client-experimental-identity-and-auth": { + "client-identity-and-auth": { "transforms": [ { "name": "includeServices", @@ -41,17 +41,16 @@ "plugins": { "typescript-client-codegen": { "service": "example.weather#Weather", - "package": "@smithy/weather-experimental-identity-and-auth", + "package": "weather", "packageVersion": "0.0.1", "packageJson": { "license": "Apache-2.0", "private": true - }, - "experimentalIdentityAndAuth": true + } } } }, - "control-experimental-identity-and-auth": { + "client-legacy-auth": { "transforms": [ { "name": "includeServices", @@ -63,12 +62,13 @@ "plugins": { "typescript-client-codegen": { "service": "example.weather#Weather", - "package": "weather", + "package": "@smithy/weather-legacy-auth", "packageVersion": "0.0.1", "packageJson": { "license": "Apache-2.0", "private": true - } + }, + "useLegacyAuth": true } } }, @@ -89,8 +89,7 @@ "packageJson": { "license": "Apache-2.0", "private": true - }, - "experimentalIdentityAndAuth": true + } } } }, @@ -111,8 +110,7 @@ "packageJson": { "license": "Apache-2.0", "private": true - }, - "experimentalIdentityAndAuth": true + } } } } diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java index 0f4011f1966..02388f69619 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java @@ -30,6 +30,7 @@ import java.util.Optional; import java.util.Set; import java.util.TreeSet; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import software.amazon.smithy.build.FileManifest; @@ -56,7 +57,9 @@ import software.amazon.smithy.typescript.codegen.sections.CommandBodyExtraCodeSection; import software.amazon.smithy.typescript.codegen.sections.CommandConstructorCodeSection; import software.amazon.smithy.typescript.codegen.sections.CommandPropertiesCodeSection; +import software.amazon.smithy.typescript.codegen.sections.PreCommandClassCodeSection; import software.amazon.smithy.typescript.codegen.sections.SmithyContextCodeSection; +import software.amazon.smithy.typescript.codegen.util.CommandWriterConsumer; import software.amazon.smithy.typescript.codegen.validation.SensitiveDataFinder; import software.amazon.smithy.utils.SmithyInternalApi; @@ -154,6 +157,17 @@ private void generateClientCommand() { ); } + // Section of items like TypeScript @ts-ignore + writer.injectSection(PreCommandClassCodeSection.builder() + .settings(settings) + .model(model) + .service(service) + .operation(operation) + .symbolProvider(symbolProvider) + .runtimeClientPlugins(runtimePlugins) + .protocolGenerator(protocolGenerator) + .applicationProtocol(applicationProtocol) + .build()); writer.openBlock( "export class $L extends $$Command.classBuilder<$T, $T, $L, ServiceInputTypes, ServiceOutputTypes>()", ".build() {", // class open bracket. @@ -459,10 +473,7 @@ private void addCommandSpecificPlugins() { // Construct additional parameters string Map paramsMap = plugin.getAdditionalPluginFunctionParameters( model, service, operation); - List additionalParameters = CodegenUtils.getFunctionParametersList(paramsMap); - String additionalParamsString = additionalParameters.isEmpty() - ? "" - : ", { " + String.join(", ", additionalParameters) + " }"; + // Construct writer context Map symbolMap = new HashMap<>(); @@ -474,7 +485,33 @@ private void addCommandSpecificPlugins() { } writer.pushState(); writer.putContext(symbolMap); - writer.write("$pluginFn:T(config" + additionalParamsString + "),"); + writer.openBlock("$pluginFn:T(config", "),", () -> { + List additionalParameters = CodegenUtils.getFunctionParametersList(paramsMap); + Map clientAddParamsWriterConsumers = + plugin.getOperationAddParamsWriterConsumers(); + if (additionalParameters.isEmpty() && clientAddParamsWriterConsumers.isEmpty()) { + return; + } + writer.openBlock(", { ", " }", () -> { + // caution: using String.join instead of templating + // because additionalParameters may contain Smithy syntax. + if (!additionalParameters.isEmpty()) { + writer.writeInline(String.join(", ", additionalParameters) + ", "); + } + clientAddParamsWriterConsumers.forEach((key, consumer) -> { + writer.writeInline("$L: $C,", key, (Consumer) (w -> { + consumer.accept(w, CommandConstructorCodeSection.builder() + .settings(settings) + .model(model) + .service(service) + .symbolProvider(symbolProvider) + .runtimeClientPlugins(runtimePlugins) + .applicationProtocol(applicationProtocol) + .build()); + })); + }); + }); + }); writer.popState(); }); } diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/DirectedTypeScriptCodegen.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/DirectedTypeScriptCodegen.java index f70ab9cc58b..fea50a6e42d 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/DirectedTypeScriptCodegen.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/DirectedTypeScriptCodegen.java @@ -102,6 +102,11 @@ public TypeScriptCodegenContext createContext(CreateContextDirective { + LOGGER.info(() -> "Mutating plugins from TypeScriptIntegration: " + integration.name()); + integration.mutateClientPlugins(runtimePlugins); + }); + ProtocolGenerator protocolGenerator = resolveProtocolGenerator( directive.integrations(), directive.model(), @@ -236,9 +241,7 @@ private void generateClient(GenerateServiceDirective new ServiceBareBonesClientGenerator( settings, model, symbolProvider, writer, integrations, runtimePlugins, applicationProtocol).run()); - if (directive.settings().getExperimentalIdentityAndAuth()) { - // feat(experimentalIdentityAndAuth): allow configuring custom HttpAuthSchemeProviderGenerator - LOGGER.fine("experimentalIdentityAndAuth: Generating auth scheme resolver"); + if (!directive.settings().useLegacyAuth()) { new HttpAuthSchemeProviderGenerator( delegator, settings, diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/RuntimeConfigGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/RuntimeConfigGenerator.java index bc1ba5a35fd..9fce5fa7693 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/RuntimeConfigGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/RuntimeConfigGenerator.java @@ -195,10 +195,9 @@ void generate(LanguageTarget target) { integration.getRuntimeConfigWriters(settings, model, symbolProvider, target) ); } - // feat(experimentalIdentityAndAuth): add config writers for httpAuthScheme and httpAuthSchemes // Needs a separate integration point since not all the information is accessible in // {@link TypeScriptIntegration#getRuntimeConfigWriters()} - if (applicationProtocol.isHttpProtocol() && settings.getExperimentalIdentityAndAuth()) { + if (applicationProtocol.isHttpProtocol() && !settings.useLegacyAuth()) { generateHttpAuthSchemeConfig(configs, writer, target); } int indentation = target.equals(LanguageTarget.SHARED) ? 1 : 2; @@ -229,7 +228,6 @@ private void generateHttpAuthSchemeConfig( ) { SupportedHttpAuthSchemesIndex authIndex = new SupportedHttpAuthSchemesIndex(integrations, model, settings); - // feat(experimentalIdentityAndAuth): write the default imported HttpAuthSchemeProvider if (target.equals(LanguageTarget.SHARED)) { configs.put("httpAuthSchemeProvider", w -> { w.write("$T", Symbol.builder() @@ -241,7 +239,6 @@ private void generateHttpAuthSchemeConfig( }); } - // feat(experimentalIdentityAndAuth): gather HttpAuthSchemes to generate ServiceIndex serviceIndex = ServiceIndex.of(model); TopDownIndex topDownIndex = TopDownIndex.of(model); Map allEffectiveHttpAuthSchemes = @@ -268,7 +265,6 @@ private void generateHttpAuthSchemeConfig( return; } - // feat(experimentalIdentityAndAuth): write the default httpAuthSchemes configs.put("httpAuthSchemes", w -> { w.addDependency(TypeScriptDependency.SMITHY_TYPES); w.addImport("IdentityProviderConfig", null, TypeScriptDependency.SMITHY_TYPES); diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServiceBareBonesClientGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServiceBareBonesClientGenerator.java index 3d5feebe53f..6be0ef279c1 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServiceBareBonesClientGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServiceBareBonesClientGenerator.java @@ -41,6 +41,7 @@ import software.amazon.smithy.typescript.codegen.sections.ClientConstructorCodeSection; import software.amazon.smithy.typescript.codegen.sections.ClientDestroyCodeSection; import software.amazon.smithy.typescript.codegen.sections.ClientPropertiesCodeSection; +import software.amazon.smithy.typescript.codegen.util.ClientWriterConsumer; import software.amazon.smithy.utils.OptionalUtils; import software.amazon.smithy.utils.SmithyInternalApi; @@ -63,13 +64,13 @@ public final class ServiceBareBonesClientGenerator implements Runnable { private final ApplicationProtocol applicationProtocol; ServiceBareBonesClientGenerator( - TypeScriptSettings settings, - Model model, - SymbolProvider symbolProvider, - TypeScriptWriter writer, - List integrations, - List runtimePlugins, - ApplicationProtocol applicationProtocol + TypeScriptSettings settings, + Model model, + SymbolProvider symbolProvider, + TypeScriptWriter writer, + List integrations, + List runtimePlugins, + ApplicationProtocol applicationProtocol ) { this.settings = settings; this.model = model; @@ -438,10 +439,6 @@ private void generateConstructor() { // Construct additional parameters string Map paramsMap = plugin.getAdditionalPluginFunctionParameters( model, service, null); - List additionalParameters = CodegenUtils.getFunctionParametersList(paramsMap); - String additionalParamsString = additionalParameters.isEmpty() - ? "" - : ", { " + String.join(", ", additionalParameters) + " }"; // Construct writer context Map symbolMap = new HashMap<>(); @@ -453,11 +450,39 @@ private void generateConstructor() { } writer.pushState(); writer.putContext(symbolMap); - writer.write("this.middlewareStack.use($pluginFn:T(this.config" + additionalParamsString + "));"); + writer.openBlock("this.middlewareStack.use($pluginFn:T(this.config", "));", () -> { + List additionalParameters = CodegenUtils.getFunctionParametersList(paramsMap); + Map clientAddParamsWriterConsumers = + plugin.getClientAddParamsWriterConsumers(); + + if (additionalParameters.isEmpty() && clientAddParamsWriterConsumers.isEmpty()) { + return; + } + + writer.openBlock(", {", " }", () -> { + // caution: using String.join instead of templating + // because additionalParameters may contain Smithy syntax. + if (!additionalParameters.isEmpty()) { + writer.writeInline(String.join(", ", additionalParameters) + ", "); + } + clientAddParamsWriterConsumers.forEach((key, consumer) -> { + writer.writeInline("$L: $C,", key, (Consumer) (w -> { + consumer.accept(w, ClientBodyExtraCodeSection.builder() + .settings(settings) + .model(model) + .service(service) + .symbolProvider(symbolProvider) + .integrations(integrations) + .runtimeClientPlugins(runtimePlugins) + .applicationProtocol(applicationProtocol) + .build()); + })); + }); + }); + }); writer.popState(); }); } - writer.popState(); }); } diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/TypeScriptDependency.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/TypeScriptDependency.java index b3416639490..0faaff2c523 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/TypeScriptDependency.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/TypeScriptDependency.java @@ -113,7 +113,7 @@ public enum TypeScriptDependency implements Dependency { AWS_SDK_QUERYSTRING_BUILDER("dependencies", "@smithy/querystring-builder", false), // Conditionally added when XML parser needs to be used. - XML_PARSER("dependencies", "fast-xml-parser", "4.2.5", false), + XML_PARSER("dependencies", "fast-xml-parser", "4.4.1", false), HTML_ENTITIES("dependencies", "entities", "2.2.0", false), // Conditionally added when streaming blob response payload exists. @@ -124,7 +124,6 @@ public enum TypeScriptDependency implements Dependency { // Conditionally added when @aws.auth#sigv4 is used SIGNATURE_V4("dependencies", "@smithy/signature-v4", false), - // feat(experimentalIdentityAndAuth): Conditionally added dependencies for `experimentalIdentityAndAuth`. // This package should never have a major version, and should only use minor and patch versions in development. // Exports are located between @smithy/types and @smithy/core @Deprecated EXPERIMENTAL_IDENTITY_AND_AUTH("dependencies", "@smithy/experimental-identity-and-auth", false), diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/TypeScriptSettings.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/TypeScriptSettings.java index a64da907a3d..0ed8bff53d7 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/TypeScriptSettings.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/TypeScriptSettings.java @@ -57,7 +57,7 @@ public final class TypeScriptSettings { private static final String PRIVATE = "private"; private static final String PACKAGE_MANAGER = "packageManager"; private static final String CREATE_DEFAULT_README = "createDefaultReadme"; - private static final String EXPERIMENTAL_IDENTITY_AND_AUTH = "experimentalIdentityAndAuth"; + private static final String USE_LEGACY_AUTH = "useLegacyAuth"; private static final String GENERATE_TYPEDOC = "generateTypeDoc"; private String packageName; @@ -75,7 +75,7 @@ public final class TypeScriptSettings { RequiredMemberMode.NULLABLE; private PackageManager packageManager = PackageManager.YARN; private boolean createDefaultReadme = false; - private boolean experimentalIdentityAndAuth = false; + private boolean useLegacyAuth = false; private boolean generateTypeDoc = false; @Deprecated @@ -110,8 +110,8 @@ public static TypeScriptSettings from(Model model, ObjectNode config, ArtifactTy settings.setPrivate(config.getBooleanMember(PRIVATE).map(BooleanNode::getValue).orElse(false)); settings.setCreateDefaultReadme( config.getBooleanMember(CREATE_DEFAULT_README).map(BooleanNode::getValue).orElse(false)); - settings.setExperimentalIdentityAndAuth( - config.getBooleanMemberOrDefault(EXPERIMENTAL_IDENTITY_AND_AUTH, false)); + settings.useLegacyAuth( + config.getBooleanMemberOrDefault(USE_LEGACY_AUTH, false)); settings.setGenerateTypeDoc( config.getBooleanMember(GENERATE_TYPEDOC).map(BooleanNode::getValue).orElse(false)); settings.setPackageManager( @@ -360,28 +360,27 @@ public void setPackageManager(PackageManager packageManager) { } /** - * Returns whether to use experimental identity and auth. + * Returns whether to use legacy auth integrations. * - * @return if experimental identity and auth should used. Default: false + * @return if legacy auth should used. Default: false */ - public boolean getExperimentalIdentityAndAuth() { - return experimentalIdentityAndAuth; + public boolean useLegacyAuth() { + return useLegacyAuth; } /** - * Sets whether experimental identity and auth should be used. + * Sets whether legacy auth should be used. * - * @param experimentalIdentityAndAuth whether experimental identity and auth should be used. + * @param useLegacyAuth whether legacy auth should be used. */ - public void setExperimentalIdentityAndAuth(boolean experimentalIdentityAndAuth) { - if (experimentalIdentityAndAuth) { + public void useLegacyAuth(boolean useLegacyAuth) { + if (useLegacyAuth) { LOGGER.warning(""" - Experimental identity and auth is in development, and is subject to \ - breaking changes. Behavior may NOT have the same feature parity as \ - non-experimental behavior. This setting is also subject to removal \ - when the feature is completed."""); + Legacy auth is considered deprecated and is no longer in development, + and should only be used for backward compatibility concerns. Consider + migrating to the default identity and auth behavior."""); } - this.experimentalIdentityAndAuth = experimentalIdentityAndAuth; + this.useLegacyAuth = useLegacyAuth; } /** @@ -490,7 +489,7 @@ public enum ArtifactType { CLIENT(SymbolVisitor::new, Arrays.asList(PACKAGE, PACKAGE_DESCRIPTION, PACKAGE_JSON, PACKAGE_VERSION, PACKAGE_MANAGER, SERVICE, PROTOCOL, PRIVATE, REQUIRED_MEMBER_MODE, - CREATE_DEFAULT_README, EXPERIMENTAL_IDENTITY_AND_AUTH, GENERATE_TYPEDOC)), + CREATE_DEFAULT_README, USE_LEGACY_AUTH, GENERATE_TYPEDOC)), SSDK((m, s) -> new ServerSymbolVisitor(m, new SymbolVisitor(m, s)), Arrays.asList(PACKAGE, PACKAGE_DESCRIPTION, PACKAGE_JSON, PACKAGE_VERSION, PACKAGE_MANAGER, SERVICE, PROTOCOL, PRIVATE, REQUIRED_MEMBER_MODE, diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/ConfigField.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/ConfigField.java index 50a53478544..e99965ab3a1 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/ConfigField.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/ConfigField.java @@ -19,7 +19,7 @@ /** * Definition of a Config field. * - * Currently used to populate the ClientDefaults interface in `experimentalIdentityAndAuth`. + * Currently used to populate the ClientDefaults interface. * * @param name name of the config field * @param type whether the config field is main or auxiliary diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/HttpAuthScheme.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/HttpAuthScheme.java index 2078c34171a..ce5607b1c3d 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/HttpAuthScheme.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/HttpAuthScheme.java @@ -22,7 +22,7 @@ import software.amazon.smithy.utils.ToSmithyBuilder; /** - * feat(experimentalIdentityAndAuth): Defines an HttpAuthScheme used in code generation. + * Defines an HttpAuthScheme used in code generation. * * HttpAuthScheme defines everything needed to generate an HttpAuthSchemeProvider, * HttpAuthOptions, and registered HttpAuthSchemes in the IdentityProviderConfiguration. diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/HttpAuthSchemeParameter.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/HttpAuthSchemeParameter.java index 7c7c58d89bb..e83fed5bdf5 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/HttpAuthSchemeParameter.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/HttpAuthSchemeParameter.java @@ -14,7 +14,7 @@ /** * Definition of an HttpAuthSchemeParameter. * - * Currently this is used to generate the the HttpAuthSchemeParameters interface in `experimentalIdentityAndAuth`. + * Currently this is used to generate the the HttpAuthSchemeParameters interface. * * @param name name of the auth scheme parameter * @param type writer for the type of the auth scheme parameter diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/HttpAuthSchemeProviderGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/HttpAuthSchemeProviderGenerator.java index fa629113874..32bdc2c8218 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/HttpAuthSchemeProviderGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/HttpAuthSchemeProviderGenerator.java @@ -41,7 +41,7 @@ import software.amazon.smithy.utils.StringUtils; /** - * feat(experimentalIdentityAndAuth): Generator for {@code HttpAuthSchemeProvider} and corresponding interfaces. + * Generator for {@code HttpAuthSchemeProvider} and corresponding interfaces. * * Code generated includes: * diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/SupportedHttpAuthSchemesIndex.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/SupportedHttpAuthSchemesIndex.java index 333d2f70c76..cc51c7b2c04 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/SupportedHttpAuthSchemesIndex.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/SupportedHttpAuthSchemesIndex.java @@ -20,9 +20,6 @@ * * Integrations may mutate this index to customize {@link HttpAuthScheme} * implementations. - * - * This class is currently under the `experimentalIdentityAgitndAuth` experimental - * flag, and is subject to breaking changes. */ @SmithyInternalApi public final class SupportedHttpAuthSchemesIndex { diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/AddHttpAuthSchemePlugin.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/AddHttpAuthSchemePlugin.java index bcb0db1a66d..30b4775b0eb 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/AddHttpAuthSchemePlugin.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/AddHttpAuthSchemePlugin.java @@ -33,8 +33,7 @@ import software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin; import software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin.Convention; import software.amazon.smithy.typescript.codegen.sections.ClientBodyExtraCodeSection; -import software.amazon.smithy.utils.CodeInterceptor; -import software.amazon.smithy.utils.CodeSection; +import software.amazon.smithy.typescript.codegen.util.ClientWriterConsumer; import software.amazon.smithy.utils.SmithyInternalApi; /** @@ -43,15 +42,19 @@ @SmithyInternalApi public final class AddHttpAuthSchemePlugin implements HttpAuthTypeScriptIntegration { /** - * Integration should only be used if `experimentalIdentityAndAuth` flag is true. + * Integration should be skipped if the `useLegacyAuth` flag is true. */ @Override public boolean matchesSettings(TypeScriptSettings settings) { - return settings.getExperimentalIdentityAndAuth(); + return !settings.useLegacyAuth(); } @Override public List getClientPlugins() { + Map httpAuthSchemeParametersProvider = Map.of( + "httpAuthSchemeParametersProvider", AddHttpAuthSchemePlugin::httpAuthSchemeParametersProvider, + "identityProviderConfigProvider", AddHttpAuthSchemePlugin::identityProviderConfigProvider + ); return List.of( RuntimeClientPlugin.builder() .servicePredicate((m, s) -> s.hasTrait(EndpointRuleSetTrait.ID)) @@ -59,14 +62,7 @@ public List getClientPlugins() { TypeScriptDependency.SMITHY_CORE.dependency, "HttpAuthSchemeEndpointRuleSet", Convention.HAS_MIDDLEWARE) - .additionalPluginFunctionParamsSupplier((model, service, operation) -> Map.of( - "httpAuthSchemeParametersProvider", Symbol.builder() - .name("this.getDefaultHttpAuthSchemeParametersProvider()") - .build(), - "identityProviderConfigProvider", Symbol.builder() - .name("this.getIdentityProviderConfigProvider()") - .build() - )) + .withAdditionalClientParams(httpAuthSchemeParametersProvider) .build(), RuntimeClientPlugin.builder() .servicePredicate((m, s) -> !s.hasTrait(EndpointRuleSetTrait.ID)) @@ -74,14 +70,7 @@ public List getClientPlugins() { TypeScriptDependency.SMITHY_CORE.dependency, "HttpAuthScheme", Convention.HAS_MIDDLEWARE) - .additionalPluginFunctionParamsSupplier((model, service, operation) -> Map.of( - "httpAuthSchemeParametersProvider", Symbol.builder() - .name("this.getDefaultHttpAuthSchemeParametersProvider()") - .build(), - "identityProviderConfigProvider", Symbol.builder() - .name("this.getIdentityProviderConfigProvider()") - .build() - )) + .withAdditionalClientParams(httpAuthSchemeParametersProvider) .build(), RuntimeClientPlugin.builder() .inputConfig(Symbol.builder() @@ -100,74 +89,10 @@ public List getClientPlugins() { ); } - @Override - public List> interceptors( - TypeScriptCodegenContext codegenContext - ) { - return List.of(CodeInterceptor.appender(ClientBodyExtraCodeSection.class, (w, s) -> { - if (!s.getSettings().generateClient() - || !s.getSettings().getExperimentalIdentityAndAuth() - || !s.getApplicationProtocol().isHttpProtocol()) { - return; - } - - /* - private getDefaultHttpAuthSchemeParametersProvider() { - return defaultWeatherHttpAuthSchemeParametersProvider; - } - */ - w.openBlock("private getDefaultHttpAuthSchemeParametersProvider() {", "}", () -> { - String httpAuthSchemeParametersProviderName = "default" - + CodegenUtils.getServiceName(s.getSettings(), s.getModel(), s.getSymbolProvider()) - + "HttpAuthSchemeParametersProvider"; - w.addImport(httpAuthSchemeParametersProviderName, null, AuthUtils.AUTH_HTTP_PROVIDER_DEPENDENCY); - w.write("return " + httpAuthSchemeParametersProviderName + ";"); - }); - - /* - private getIdentityProviderConfigProvider() { - return async (config: WeatherClientResolvedConfig) => new DefaultIdentityProviderConfig({ - "aws.auth#sigv4": config.credentials, - "smithy.api#httpApiKeyAuth": config.apiKey, - "smithy.api#httpBearerAuth": config.token, - }); - } - */ - w.openBlock("private getIdentityProviderConfigProvider() {", "}", () -> { - w.addDependency(TypeScriptDependency.SMITHY_CORE); - w.addImport("DefaultIdentityProviderConfig", null, TypeScriptDependency.SMITHY_CORE); - w.openBlock(""" - return async (config: $LResolvedConfig) => \ - new DefaultIdentityProviderConfig({""", "});", - s.getSymbolProvider().toSymbol(s.getService()).getName(), - () -> { - SupportedHttpAuthSchemesIndex authIndex = new SupportedHttpAuthSchemesIndex( - s.getIntegrations(), - s.getModel(), - s.getSettings()); - ServiceIndex serviceIndex = ServiceIndex.of(s.getModel()); - TopDownIndex topDownIndex = TopDownIndex.of(s.getModel()); - Map httpAuthSchemes = AuthUtils.getAllEffectiveNoAuthAwareAuthSchemes( - s.getService(), serviceIndex, authIndex, topDownIndex); - for (HttpAuthScheme scheme : httpAuthSchemes.values()) { - if (scheme == null) { - continue; - } - for (ConfigField configField : scheme.getConfigFields()) { - if (configField.type().equals(ConfigField.Type.MAIN)) { - w.write("$S: config.$L,", scheme.getSchemeId().toString(), configField.name()); - } - } - } - }); - }); - })); - } - @Override public void customize(TypeScriptCodegenContext codegenContext) { if (!codegenContext.settings().generateClient() - || !codegenContext.settings().getExperimentalIdentityAndAuth() + || codegenContext.settings().useLegacyAuth() || !codegenContext.applicationProtocol().isHttpProtocol()) { return; } @@ -217,6 +142,71 @@ public void customize(TypeScriptCodegenContext codegenContext) { }); } + /** + * Writes the httpAuthSchemeParametersProvider for input to middleware additional parameters. + * Example: + * ```typescript + * defaultWeatherHttpAuthSchemeParametersProvider; + * ``` + */ + private static void httpAuthSchemeParametersProvider(TypeScriptWriter w, + ClientBodyExtraCodeSection clientBodySection) { + String httpAuthSchemeParametersProviderName = "default" + + CodegenUtils.getServiceName( + clientBodySection.getSettings(), + clientBodySection.getModel(), + clientBodySection.getSymbolProvider() + ) + + "HttpAuthSchemeParametersProvider"; + w.addImport(httpAuthSchemeParametersProviderName, null, AuthUtils.AUTH_HTTP_PROVIDER_DEPENDENCY); + w.writeInline(httpAuthSchemeParametersProviderName); + } + + /** + * Writes the identityProviderConfigProvider for input to middleware additional parameters. + * Example: + * ```typescript + * async (config: WeatherClientResolvedConfig) => new DefaultIdentityProviderConfig({ + * "aws.auth#sigv4": config.credentials, + * "smithy.api#httpApiKeyAuth": config.apiKey, + * "smithy.api#httpBearerAuth": config.token, + * }) + * ``` + */ + private static void identityProviderConfigProvider(TypeScriptWriter w, + ClientBodyExtraCodeSection s) { + w.addDependency(TypeScriptDependency.SMITHY_CORE); + w.addImport("DefaultIdentityProviderConfig", null, TypeScriptDependency.SMITHY_CORE); + w.openBlock(""" + async (config: $LResolvedConfig) => \ + new DefaultIdentityProviderConfig({""", "})", + s.getSymbolProvider().toSymbol(s.getService()).getName(), + () -> { + SupportedHttpAuthSchemesIndex authIndex = new SupportedHttpAuthSchemesIndex( + s.getIntegrations(), + s.getModel(), + s.getSettings()); + ServiceIndex serviceIndex = ServiceIndex.of(s.getModel()); + TopDownIndex topDownIndex = TopDownIndex.of(s.getModel()); + Map httpAuthSchemes = AuthUtils.getAllEffectiveNoAuthAwareAuthSchemes( + s.getService(), serviceIndex, authIndex, topDownIndex); + for (HttpAuthScheme scheme : httpAuthSchemes.values()) { + if (scheme == null) { + continue; + } + for (ConfigField configField : scheme.getConfigFields()) { + if (configField.type().equals(ConfigField.Type.MAIN)) { + w.writeInline( + "$S: config.$L,", + scheme.getSchemeId().toString(), + configField.name() + ); + } + } + } + }); + } + /* export interface HttpAuthSchemeInputConfig { httpAuthSchemes?: HttpAuthScheme[]; @@ -260,14 +250,14 @@ private void generateHttpAuthSchemeInputConfigInterface( w.addDependency(TypeScriptDependency.SMITHY_TYPES); w.addImport("HttpAuthScheme", null, TypeScriptDependency.SMITHY_TYPES); w.writeDocs(""" - experimentalIdentityAndAuth: Configuration of HttpAuthSchemes for a client which provides \ + Configuration of HttpAuthSchemes for a client which provides \ default identity providers and signers per auth scheme. @internal"""); w.write("httpAuthSchemes?: HttpAuthScheme[];\n"); String httpAuthSchemeProviderName = serviceName + "HttpAuthSchemeProvider"; w.writeDocs(""" - experimentalIdentityAndAuth: Configuration of an HttpAuthSchemeProvider for a client which \ + Configuration of an HttpAuthSchemeProvider for a client which \ resolves which HttpAuthScheme to use. @internal"""); w.write("httpAuthSchemeProvider?: $L;\n", httpAuthSchemeProviderName); @@ -326,14 +316,14 @@ private void generateHttpAuthSchemeResolvedConfigInterface( w.addDependency(TypeScriptDependency.SMITHY_TYPES); w.addImport("HttpAuthScheme", null, TypeScriptDependency.SMITHY_TYPES); w.writeDocs(""" - experimentalIdentityAndAuth: Configuration of HttpAuthSchemes for a client which provides \ + Configuration of HttpAuthSchemes for a client which provides \ default identity providers and signers per auth scheme. @internal"""); w.write("readonly httpAuthSchemes: HttpAuthScheme[];\n"); String httpAuthSchemeProviderName = serviceName + "HttpAuthSchemeProvider"; w.writeDocs(""" - experimentalIdentityAndAuth: Configuration of an HttpAuthSchemeProvider for a client which \ + Configuration of an HttpAuthSchemeProvider for a client which \ resolves which HttpAuthScheme to use. @internal"""); w.write("readonly httpAuthSchemeProvider: $L;\n", httpAuthSchemeProviderName); diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/AddHttpSigningPlugin.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/AddHttpSigningPlugin.java index fbdc5c99223..9b8a43e258d 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/AddHttpSigningPlugin.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/AddHttpSigningPlugin.java @@ -16,17 +16,15 @@ /** * Add middleware for {@code httpSigningMiddleware}. - * - * This is the experimental behavior for `experimentalIdentityAndAuth`. */ @SmithyInternalApi public class AddHttpSigningPlugin implements TypeScriptIntegration { /** - * Integration should only be used if `experimentalIdentityAndAuth` flag is true. + * Integration should be skipped if the `useLegacyAuth` flag is true. */ @Override public boolean matchesSettings(TypeScriptSettings settings) { - return settings.getExperimentalIdentityAndAuth(); + return !settings.useLegacyAuth(); } @Override diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/HttpAuthExtensionConfigurationInterface.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/HttpAuthExtensionConfigurationInterface.java index db90da40c42..c91190a0b43 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/HttpAuthExtensionConfigurationInterface.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/HttpAuthExtensionConfigurationInterface.java @@ -13,8 +13,6 @@ /** * Adds the corresponding interface and functions for {@code HttpAuthExtensionConfiguration}. - * - * This is experimental for `experimentalIdentityAndAuth`. */ @SmithyInternalApi public class HttpAuthExtensionConfigurationInterface implements ExtensionConfigurationInterface { diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/HttpAuthRuntimeExtensionIntegration.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/HttpAuthRuntimeExtensionIntegration.java index ea39877ae3b..d3a0481d914 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/HttpAuthRuntimeExtensionIntegration.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/HttpAuthRuntimeExtensionIntegration.java @@ -28,18 +28,16 @@ /** * Adds {@link HttpAuthExtensionConfigurationInterface} to a client. - * - * This is the experimental behavior for `experimentalIdentityAndAuth`. */ @SmithyInternalApi public class HttpAuthRuntimeExtensionIntegration implements TypeScriptIntegration { /** - * Integration should only be used if `experimentalIdentityAndAuth` flag is true. + * Integration should be skipped if the `useLegacyAuth` flag is true. */ @Override public boolean matchesSettings(TypeScriptSettings settings) { - return settings.getExperimentalIdentityAndAuth(); + return !settings.useLegacyAuth(); } @Override diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/HttpAuthTypeScriptIntegration.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/HttpAuthTypeScriptIntegration.java index 77d4006972a..bbd936eef52 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/HttpAuthTypeScriptIntegration.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/HttpAuthTypeScriptIntegration.java @@ -14,15 +14,13 @@ import software.amazon.smithy.utils.SmithyInternalApi; /** - * Java SPI for customizing TypeScript code generation for `experimentalIdentityAndAuth`. - * - * This should NOT be used as it is highly susceptible to breaking changes. + * Java SPI for customizing TypeScript code generation for Identity and Authentication. */ @SmithyInternalApi public interface HttpAuthTypeScriptIntegration extends TypeScriptIntegration { /** - * feat(experimentalIdentityAndAuth): Register an {@link HttpAuthScheme} that is used to generate the - * {@code HttpAuthSchemeProvider} and corresponding config field and runtime config values. + * Register an {@link HttpAuthScheme} that is used to generate the {@code HttpAuthSchemeProvider} + * and corresponding config field and runtime config values. * @return an empty optional. */ default Optional getHttpAuthScheme() { @@ -30,8 +28,8 @@ default Optional getHttpAuthScheme() { } /** - * feat(experimentalIdentityAndAuth): Mutate an {@link SupportedHttpAuthSchemesIndex} to mutate registered - * {@link HttpAuthScheme}s, e.g. default {@code IdentityProvider}s and {@code HttpSigner}s. + * Mutate an {@link SupportedHttpAuthSchemesIndex} to mutate registered {@link HttpAuthScheme}s, + * e.g. default {@code IdentityProvider}s and {@code HttpSigner}s. * @param supportedHttpAuthSchemesIndex index to mutate. * @param model model * @param settings settings diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/SupportHttpApiKeyAuth.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/SupportHttpApiKeyAuth.java index 00b32335122..8ce8d9db999 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/SupportHttpApiKeyAuth.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/SupportHttpApiKeyAuth.java @@ -23,8 +23,6 @@ /** * Support for @httpApiKeyAuth. - * - * This is the experimental behavior for `experimentalIdentityAndAuth`. */ @SmithyInternalApi public class SupportHttpApiKeyAuth implements HttpAuthTypeScriptIntegration { @@ -51,11 +49,11 @@ public class SupportHttpApiKeyAuth implements HttpAuthTypeScriptIntegration { .build(); /** - * Integration should only be used if `experimentalIdentityAndAuth` flag is true. + * Integration should be skipped if the `useLegacyAuth` flag is true. */ @Override public boolean matchesSettings(TypeScriptSettings settings) { - return settings.getExperimentalIdentityAndAuth(); + return !settings.useLegacyAuth(); } @Override diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/SupportHttpBearerAuth.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/SupportHttpBearerAuth.java index f5ff5d94482..0879f3e7c5d 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/SupportHttpBearerAuth.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/SupportHttpBearerAuth.java @@ -21,8 +21,6 @@ /** * Support for @httpBearerAuth. - * - * This is the experimental behavior for `experimentalIdentityAndAuth`. */ @SmithyInternalApi public final class SupportHttpBearerAuth implements HttpAuthTypeScriptIntegration { @@ -44,11 +42,11 @@ public final class SupportHttpBearerAuth implements HttpAuthTypeScriptIntegratio .build(); /** - * Integration should only be used if `experimentalIdentityAndAuth` flag is true. + * Integration should be skipped if the `useLegacyAuth` flag is true. */ @Override public boolean matchesSettings(TypeScriptSettings settings) { - return settings.getExperimentalIdentityAndAuth(); + return !settings.useLegacyAuth(); } @Override diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/SupportNoAuth.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/SupportNoAuth.java index 5025c26b18c..0c86191c808 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/SupportNoAuth.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/http/integration/SupportNoAuth.java @@ -32,11 +32,11 @@ public final class SupportNoAuth implements HttpAuthTypeScriptIntegration { .build()); /** - * Integration should only be used if `experimentalIdentityAndAuth` flag is true. + * Integration should be skipped if the `useLegacyAuth` flag is true. */ @Override public boolean matchesSettings(TypeScriptSettings settings) { - return settings.getExperimentalIdentityAndAuth(); + return !settings.useLegacyAuth(); } @Override diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/EndpointsV2Generator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/EndpointsV2Generator.java index f85201e1f50..9db0a0529bc 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/EndpointsV2Generator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/EndpointsV2Generator.java @@ -117,7 +117,10 @@ private void generateEndpointParameters() { RuleSetParameterFinder ruleSetParameterFinder = new RuleSetParameterFinder(service); Map clientInputParams = ruleSetParameterFinder.getClientContextParams(); - clientInputParams.putAll(ruleSetParameterFinder.getBuiltInParams()); + //Omit Endpoint params that should not be a part of the ClientInputEndpointParameters interface + Map builtInParams = ruleSetParameterFinder.getBuiltInParams(); + builtInParams.keySet().removeIf(OmitEndpointParams::isOmitted); + clientInputParams.putAll(builtInParams); ObjectNode ruleSet = endpointRuleSetTrait.getRuleSet().expectObjectNode(); ruleSet.getObjectMember("parameters").ifPresent(parameters -> { diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/OmitEndpointParams.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/OmitEndpointParams.java new file mode 100644 index 00000000000..7c94fe61f38 --- /dev/null +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/OmitEndpointParams.java @@ -0,0 +1,33 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + + package software.amazon.smithy.typescript.codegen.endpointsV2; + + import java.util.Collections; + import java.util.HashSet; + import java.util.Set; + + /** + * Manages a collection of endpoint parameter names to be omitted from a specific interface. + * While this could be extensible in the future, as of right now, + * this collection is maintaining endpoint params to be omitted from the `ClientInputEndpointParameters` interface. + */ + public final class OmitEndpointParams { + private static final Set OMITTED_PARAMS = new HashSet<>(); + + private OmitEndpointParams() {} + + public static void addOmittedParams(Set paramNames) { + OMITTED_PARAMS.addAll(paramNames); + } + + public static boolean isOmitted(String paramName) { + return OMITTED_PARAMS.contains(paramName); + } + + public static Set getOmittedParams() { + return Collections.unmodifiableSet(OMITTED_PARAMS); + } +} diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/RuleSetParameterFinder.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/RuleSetParameterFinder.java index 01b1e6fbe93..a76ad95d6be 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/RuleSetParameterFinder.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/RuleSetParameterFinder.java @@ -30,7 +30,9 @@ import software.amazon.smithy.rulesengine.traits.ContextParamTrait; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; import software.amazon.smithy.rulesengine.traits.StaticContextParamsTrait; +import software.amazon.smithy.utils.SmithyInternalApi; +@SmithyInternalApi public class RuleSetParameterFinder { private final ServiceShape service; private final EndpointRuleSetTrait ruleset; @@ -73,31 +75,6 @@ public Map getClientContextParams() { return map; } - /** - * "The staticContextParams trait defines one or more context parameters that MUST - * be bound to the specified values. This trait MUST target an operation shape." - */ - public Map getStaticContextParams(Shape operationInput) { - Map map = new HashMap<>(); - - if (operationInput.isStructureShape()) { - operationInput.getAllMembers().forEach((String memberName, MemberShape member) -> { - Optional trait = member.getTrait(StaticContextParamsTrait.class); - if (trait.isPresent()) { - StaticContextParamsTrait staticContextParamsTrait = trait.get(); - staticContextParamsTrait.getParameters().forEach((name, definition) -> { - map.put( - name, - definition.getValue().getType().toString() - ); - }); - } - }); - } - - return map; - } - /** * Get map of params to actual values instead of the value type. */ diff --git a/smithy-typescript-ssdk-codegen-test-utils/src/main/java/software/amazon/smithy/typescript/ssdk/codegen/test/utils/AddBuiltinPlugins.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddBuiltinPlugins.java similarity index 67% rename from smithy-typescript-ssdk-codegen-test-utils/src/main/java/software/amazon/smithy/typescript/ssdk/codegen/test/utils/AddBuiltinPlugins.java rename to smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddBuiltinPlugins.java index a61dbf2b783..efc424395a6 100644 --- a/smithy-typescript-ssdk-codegen-test-utils/src/main/java/software/amazon/smithy/typescript/ssdk/codegen/test/utils/AddBuiltinPlugins.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddBuiltinPlugins.java @@ -1,29 +1,33 @@ -package software.amazon.smithy.typescript.ssdk.codegen.test.utils; +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.typescript.codegen.integration; import static software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin.Convention.HAS_CONFIG; import static software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin.Convention.HAS_MIDDLEWARE; import java.util.List; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; import software.amazon.smithy.typescript.codegen.TypeScriptDependency; -import software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin; -import software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration; -import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.SmithyInternalApi; /** - * Adds built-in plugins. + * Adds all built-in runtime client plugins to clients. */ @SmithyInternalApi public class AddBuiltinPlugins implements TypeScriptIntegration { - @Override public List getClientPlugins() { // Note that order is significant because configurations might // rely on previously resolved values. - return ListUtils.of( + return List.of( RuntimeClientPlugin.builder() .withConventions( TypeScriptDependency.CONFIG_RESOLVER.dependency, "CustomEndpoints", HAS_CONFIG) + .servicePredicate((m, s) -> !isEndpointsV2Service(s)) .build(), RuntimeClientPlugin.builder() .withConventions(TypeScriptDependency.MIDDLEWARE_RETRY.dependency, "Retry") @@ -33,5 +37,8 @@ public List getClientPlugins() { HAS_MIDDLEWARE) .build()); } + + private static boolean isEndpointsV2Service(ServiceShape serviceShape) { + return serviceShape.hasTrait(EndpointRuleSetTrait.class); + } } - \ No newline at end of file diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddEventStreamDependency.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddEventStreamDependency.java index 03144e11a93..ab8df8808a2 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddEventStreamDependency.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddEventStreamDependency.java @@ -44,6 +44,13 @@ @SmithyInternalApi public final class AddEventStreamDependency implements TypeScriptIntegration { + @Override + public List runAfter() { + return List.of( + new AddBuiltinPlugins().name() + ); + } + @Override public List getClientPlugins() { return ListUtils.of( diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddHttpApiKeyAuthPlugin.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddHttpApiKeyAuthPlugin.java index 40e8cf54ed6..a40cf61acbb 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddHttpApiKeyAuthPlugin.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddHttpApiKeyAuthPlugin.java @@ -30,7 +30,7 @@ /** * Add config and middleware to support a service with the @httpApiKeyAuth trait. * - * This is the existing control behavior for `experimentalIdentityAndAuth`. + * This is legacy auth behavior, and is no longer in development. */ @SmithyInternalApi public final class AddHttpApiKeyAuthPlugin implements TypeScriptIntegration { @@ -38,11 +38,11 @@ public final class AddHttpApiKeyAuthPlugin implements TypeScriptIntegration { public static final String INTEGRATION_NAME = "HttpApiKeyAuth"; /** - * Integration should only be used if `experimentalIdentityAndAuth` flag is false. + * Integration should be used only if the `useLegacyAuth` flag is true. */ @Override public boolean matchesSettings(TypeScriptSettings settings) { - return !settings.getExperimentalIdentityAndAuth(); + return settings.useLegacyAuth(); } /** diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/EventStreamGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/EventStreamGenerator.java index 0b6ced0f3fc..cdd717ef8ad 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/EventStreamGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/EventStreamGenerator.java @@ -16,6 +16,7 @@ package software.amazon.smithy.typescript.codegen.integration; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeSet; @@ -43,6 +44,7 @@ import software.amazon.smithy.typescript.codegen.TypeScriptWriter; import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator.GenerationContext; import software.amazon.smithy.typescript.codegen.knowledge.SerdeElisionIndex; +import software.amazon.smithy.utils.Pair; import software.amazon.smithy.utils.SmithyUnstableApi; /** @@ -102,15 +104,21 @@ public void generateEventStreamSerializers( TopDownIndex topDownIndex = TopDownIndex.of(model); Set operations = topDownIndex.getContainedOperations(service); TreeSet eventUnionsToSerialize = new TreeSet<>(); - TreeSet eventShapesToMarshall = new TreeSet<>(); + TreeSet> eventShapesToMarshall = new TreeSet<>( + (a, b) -> Objects.compare(a.getRight(), b.getRight(), StructureShape::compareTo) + ); + for (OperationShape operation : operations) { if (hasEventStreamInput(context, operation)) { UnionShape eventsUnion = getEventStreamInputShape(context, operation); eventUnionsToSerialize.add(eventsUnion); - Set eventShapes = eventsUnion.members().stream() - .map(member -> model.expectShape(member.getTarget()).asStructureShape().get()) - .collect(Collectors.toSet()); - eventShapes.forEach(eventShapesToMarshall::add); + eventsUnion.members() + .forEach(member -> { + eventShapesToMarshall.add(Pair.of( + member.getMemberName(), + model.expectShape(member.getTarget()).asStructureShape().get() + )); + }); } } @@ -118,14 +126,16 @@ public void generateEventStreamSerializers( generateEventStreamSerializer(context, eventsUnion); }); SerdeElisionIndex serdeElisionIndex = SerdeElisionIndex.of(model); - eventShapesToMarshall.forEach(event -> { + eventShapesToMarshall.forEach(memberNameAndEvent -> { generateEventMarshaller( context, - event, + memberNameAndEvent.getLeft(), + memberNameAndEvent.getRight(), documentContentType, serializeInputEventDocumentPayload, documentShapesToSerialize, - serdeElisionIndex); + serdeElisionIndex + ); }); } @@ -155,6 +165,7 @@ public void generateEventStreamDeserializers( Set operations = topDownIndex.getContainedOperations(service); TreeSet eventUnionsToDeserialize = new TreeSet<>(); TreeSet eventShapesToUnmarshall = new TreeSet<>(); + for (OperationShape operation : operations) { if (hasEventStreamOutput(context, operation)) { UnionShape eventsUnion = getEventStreamOutputShape(context, operation); @@ -237,6 +248,7 @@ private Symbol getSymbol(GenerationContext context, Shape shape) { public void generateEventMarshaller( GenerationContext context, + String memberName, StructureShape event, String documentContentType, Runnable serializeInputEventDocumentPayload, @@ -253,7 +265,7 @@ public void generateEventMarshaller( + "): __Message => {", "}", methodName, symbol, () -> { writer.openBlock("const headers: __MessageHeaders = {", "}", () -> { //fix headers required by event stream - writer.write("\":event-type\": { type: \"string\", value: $S },", symbol.getName()); + writer.write("\":event-type\": { type: \"string\", value: $S },", memberName); writer.write("\":message-type\": { type: \"string\", value: \"event\" },"); writeEventContentTypeHeader(context, event, documentContentType); }); diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/HttpBindingProtocolGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/HttpBindingProtocolGenerator.java index 8b4a5503db5..0f2761ab942 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/HttpBindingProtocolGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/HttpBindingProtocolGenerator.java @@ -1010,30 +1010,53 @@ private void writeNormalHeader(GenerationContext context, HttpBinding binding) { binding.getMember(), target ); + boolean isIdempotencyToken = binding.getMember().hasTrait(IdempotencyTokenTrait.class); + if (isIdempotencyToken) { + context.getWriter() + .addImport("v4", "generateIdempotencyToken", TypeScriptDependency.UUID); + } + + boolean headerAssertion = headerValue.endsWith("!"); + String headerBaseValue = (headerAssertion + ? headerValue.substring(0, headerValue.length() - 1) + : headerValue); if (!Objects.equals(memberLocation + "!", headerValue)) { String defaultValue = ""; if (headerBuffer.containsKey(headerKey)) { String s = headerBuffer.get(headerKey); defaultValue = " || " + s.substring(s.indexOf(": ") + 2, s.length() - 1); + } else if (isIdempotencyToken) { + defaultValue = " ?? generateIdempotencyToken()"; } + + String headerValueExpression = headerAssertion && !defaultValue.isEmpty() + ? headerBaseValue + defaultValue + : headerValue + defaultValue; + // evaluated value has a function or method call attached headerBuffer.put(headerKey, String.format( "[%s]: [() => isSerializableHeaderValue(%s), () => %s],", context.getStringStore().var(headerKey), memberLocation + defaultValue, - headerValue + defaultValue + headerValueExpression )); } else { - String value = headerValue; + String constructedHeaderValue = (headerAssertion + ? headerBaseValue + : headerValue); if (headerBuffer.containsKey(headerKey)) { String s = headerBuffer.get(headerKey); - value = headerValue + " || " + s.substring(s.indexOf(": ") + 2, s.length() - 1); + constructedHeaderValue += " || " + s.substring(s.indexOf(": ") + 2, s.length() - 1); + } else if (isIdempotencyToken) { + constructedHeaderValue += " ?? generateIdempotencyToken()"; + } else { + constructedHeaderValue = headerValue; } headerBuffer.put(headerKey, String.format( "[%s]: %s,", context.getStringStore().var(headerKey), - value + constructedHeaderValue )); } } diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/RuntimeClientPlugin.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/RuntimeClientPlugin.java index c48f730c4ad..3a51425f676 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/RuntimeClientPlugin.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/RuntimeClientPlugin.java @@ -15,11 +15,13 @@ package software.amazon.smithy.typescript.codegen.integration; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.TreeMap; import java.util.function.BiPredicate; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolDependency; @@ -29,6 +31,8 @@ import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.typescript.codegen.TypeScriptDependency; import software.amazon.smithy.typescript.codegen.TypeScriptSettings; +import software.amazon.smithy.typescript.codegen.util.ClientWriterConsumer; +import software.amazon.smithy.typescript.codegen.util.CommandWriterConsumer; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyUnstableApi; import software.amazon.smithy.utils.StringUtils; @@ -56,6 +60,8 @@ public final class RuntimeClientPlugin implements ToSmithyBuilder servicePredicate; private final OperationPredicate operationPredicate; private final SettingsPredicate settingsPredicate; + private final Map writeAdditionalClientParams; + private final Map writeAdditionalOperationParams; private RuntimeClientPlugin(Builder builder) { inputConfig = builder.inputConfig; @@ -68,6 +74,8 @@ private RuntimeClientPlugin(Builder builder) { operationPredicate = builder.operationPredicate; servicePredicate = builder.servicePredicate; settingsPredicate = builder.settingsPredicate; + writeAdditionalClientParams = builder.writeAdditionalClientParams; + writeAdditionalOperationParams = builder.writeAdditionalOperationParams; boolean allNull = (inputConfig == null) && (resolvedConfig == null) && (resolveFunction == null); boolean allSet = (inputConfig != null) && (resolvedConfig != null) && (resolveFunction != null); @@ -258,7 +266,30 @@ public Map getAdditionalPluginFunctionParameters( if (additionalPluginFunctionParamsSupplier != null) { return additionalPluginFunctionParamsSupplier.apply(model, service, operation); } - return new HashMap(); + return new HashMap<>(); + } + + /** + * Gets a list of additional parameters to be supplied to the + * plugin function. These parameters are to be supplied to plugin + * function as second argument. The map is empty if there are + * no additional parameters. + * + * @param model Model the operation belongs to. + * @param service Service the operation belongs to. + * @param operation Operation to test against. + * @return Returns the optionally present map of parameters. The key is the key + * for a parameter, and value is the value for a parameter. + */ + public Map getAdditionalPluginFunctionParameterWriterConsumers( + Model model, + ServiceShape service, + OperationShape operation + ) { + if (additionalPluginFunctionParamsSupplier != null) { + return additionalPluginFunctionParamsSupplier.apply(model, service, operation); + } + return new HashMap<>(); } /** @@ -326,6 +357,22 @@ public boolean matchesSettings(Model model, ServiceShape service, TypeScriptSett return settingsPredicate.test(model, service, settings); } + /** + * @return the map of additional client level plugin params and their writer consumers used + * to populate the param values. + */ + public Map getClientAddParamsWriterConsumers() { + return this.writeAdditionalClientParams; + } + + /** + * @return the map of additional operation level plugin params and their writer consumers used + * to populate the param values. + */ + public Map getOperationAddParamsWriterConsumers() { + return this.writeAdditionalOperationParams; + } + public static Builder builder() { return new Builder(); } @@ -398,6 +445,8 @@ public static final class Builder implements SmithyBuilder private BiPredicate servicePredicate = (model, service) -> true; private OperationPredicate operationPredicate = (model, service, operation) -> false; private SettingsPredicate settingsPredicate = (model, service, settings) -> true; + private Map writeAdditionalClientParams = Collections.emptyMap(); + private Map writeAdditionalOperationParams = Collections.emptyMap(); @Override public RuntimeClientPlugin build() { @@ -746,6 +795,26 @@ public Builder servicePredicate(BiPredicate servicePredicat return this; } + /** + * Enables access to the writer for adding imports/dependencies. + */ + public Builder withAdditionalClientParams(Map writeAdditionalClientParams) { + // enforce consistent sorting during codegen. + this.writeAdditionalClientParams = new TreeMap<>(writeAdditionalClientParams); + return this; + } + + /** + * Enables access to the writer for adding imports/dependencies. + */ + public Builder withAdditionalOperationParams( + Map writeAdditionalOperationParams + ) { + // enforce consistent sorting during codegen. + this.writeAdditionalOperationParams = new TreeMap<>(writeAdditionalOperationParams); + return this; + } + /** * Configures various aspects of the builder based on naming conventions * defined by the provided {@link Convention} values. diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/TypeScriptIntegration.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/TypeScriptIntegration.java index 83dd240eee1..75dda21d0e1 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/TypeScriptIntegration.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/TypeScriptIntegration.java @@ -64,6 +64,13 @@ default List getClientPlugins() { return Collections.emptyList(); } + /** + * Mutates in place the loaded list of plugins to apply to the generated client. + */ + default void mutateClientPlugins(List plugins) { + // defaults to no mutation + } + /** * Gets a list of protocol generators to register. * diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/sections/PreCommandClassCodeSection.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/sections/PreCommandClassCodeSection.java new file mode 100644 index 00000000000..06b3aa6317f --- /dev/null +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/sections/PreCommandClassCodeSection.java @@ -0,0 +1,135 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.typescript.codegen.sections; + +import java.util.List; +import java.util.Optional; +import software.amazon.smithy.codegen.core.SymbolProvider; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.typescript.codegen.ApplicationProtocol; +import software.amazon.smithy.typescript.codegen.TypeScriptSettings; +import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin; +import software.amazon.smithy.utils.CodeSection; +import software.amazon.smithy.utils.SmithyBuilder; +import software.amazon.smithy.utils.SmithyUnstableApi; + +@SmithyUnstableApi +public final class PreCommandClassCodeSection implements CodeSection { + private final TypeScriptSettings settings; + private final Model model; + private final ServiceShape service; + private final OperationShape operation; + private final SymbolProvider symbolProvider; + private final List runtimeClientPlugins; + private final ProtocolGenerator protocolGenerator; + private final ApplicationProtocol applicationProtocol; + + private PreCommandClassCodeSection(Builder builder) { + settings = SmithyBuilder.requiredState("settings", builder.settings); + model = SmithyBuilder.requiredState("model", builder.model); + service = SmithyBuilder.requiredState("service", builder.service); + operation = SmithyBuilder.requiredState("operation", builder.operation); + symbolProvider = SmithyBuilder.requiredState("symbolProvider", builder.symbolProvider); + runtimeClientPlugins = SmithyBuilder.requiredState("runtimeClientPlugins", builder.runtimeClientPlugins); + protocolGenerator = builder.protocolGenerator; + applicationProtocol = SmithyBuilder.requiredState("applicationProtocol", builder.applicationProtocol); + } + + public static Builder builder() { + return new Builder(); + } + + public TypeScriptSettings getSettings() { + return settings; + } + + public Model getModel() { + return model; + } + + public ServiceShape getService() { + return service; + } + + public OperationShape getOperation() { + return operation; + } + + public SymbolProvider getSymbolProvider() { + return symbolProvider; + } + + public List getRuntimeClientPlugins() { + return runtimeClientPlugins; + } + + public Optional getProtocolGenerator() { + return Optional.ofNullable(protocolGenerator); + } + + public ApplicationProtocol getApplicationProtocol() { + return applicationProtocol; + } + + public static class Builder implements SmithyBuilder { + private TypeScriptSettings settings; + private Model model; + private ServiceShape service; + private OperationShape operation; + private SymbolProvider symbolProvider; + private List runtimeClientPlugins; + private ProtocolGenerator protocolGenerator; + private ApplicationProtocol applicationProtocol; + + @Override + public PreCommandClassCodeSection build() { + return new PreCommandClassCodeSection(this); + } + + public Builder settings(TypeScriptSettings 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 operation(OperationShape operation) { + this.operation = operation; + return this; + } + + public Builder symbolProvider(SymbolProvider symbolProvider) { + this.symbolProvider = symbolProvider; + return this; + } + + public Builder runtimeClientPlugins(List runtimeClientPlugins) { + this.runtimeClientPlugins = runtimeClientPlugins; + return this; + } + + public Builder protocolGenerator(ProtocolGenerator protocolGenerator) { + this.protocolGenerator = protocolGenerator; + return this; + } + + public Builder applicationProtocol(ApplicationProtocol applicationProtocol) { + this.applicationProtocol = applicationProtocol; + return this; + } + } +} diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/util/ClientWriterConsumer.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/util/ClientWriterConsumer.java new file mode 100644 index 00000000000..62290dcfe7b --- /dev/null +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/util/ClientWriterConsumer.java @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.typescript.codegen.util; + +import software.amazon.smithy.typescript.codegen.TypeScriptWriter; +import software.amazon.smithy.typescript.codegen.sections.ClientBodyExtraCodeSection; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * The writer consumer for a RuntimeClientPlugin. May be used to add imports and dependencies + * used by the plugin at the client level. + */ +@FunctionalInterface +@SmithyInternalApi +public interface ClientWriterConsumer { + void accept(TypeScriptWriter writer, ClientBodyExtraCodeSection section); +} diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/util/CommandWriterConsumer.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/util/CommandWriterConsumer.java new file mode 100644 index 00000000000..1eed5a9db8a --- /dev/null +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/util/CommandWriterConsumer.java @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.typescript.codegen.util; + +import software.amazon.smithy.typescript.codegen.TypeScriptWriter; +import software.amazon.smithy.typescript.codegen.sections.CommandConstructorCodeSection; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * The writer consumer for a RuntimeClientPlugin. May be used to add imports and dependencies + * used by the plugin at the command level. + */ +@FunctionalInterface +@SmithyInternalApi +public interface CommandWriterConsumer { + void accept(TypeScriptWriter writer, CommandConstructorCodeSection section); +} diff --git a/smithy-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration b/smithy-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration index 5bbd45c4fdc..d1d0cf3904f 100644 --- a/smithy-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration +++ b/smithy-typescript-codegen/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration @@ -1,3 +1,4 @@ +software.amazon.smithy.typescript.codegen.integration.AddBuiltinPlugins software.amazon.smithy.typescript.codegen.integration.AddClientRuntimeConfig software.amazon.smithy.typescript.codegen.integration.AddEventStreamDependency software.amazon.smithy.typescript.codegen.integration.AddChecksumRequiredDependency diff --git a/smithy-typescript-codegen/src/main/resources/software/amazon/smithy/typescript/codegen/protocol-test-stub.ts b/smithy-typescript-codegen/src/main/resources/software/amazon/smithy/typescript/codegen/protocol-test-stub.ts index a084dad61da..dc5283c1379 100644 --- a/smithy-typescript-codegen/src/main/resources/software/amazon/smithy/typescript/codegen/protocol-test-stub.ts +++ b/smithy-typescript-codegen/src/main/resources/software/amazon/smithy/typescript/codegen/protocol-test-stub.ts @@ -1,14 +1,14 @@ import { HttpHandlerOptions, HeaderBag } from "@smithy/types"; import { HttpHandler, HttpRequest, HttpResponse } from "@smithy/protocol-http"; -import { Readable } from 'stream'; +import { Readable } from "stream"; /** * Throws an expected exception that contains the serialized request. */ class EXPECTED_REQUEST_SERIALIZATION_ERROR extends Error { - constructor(readonly request: HttpRequest) { - super(); - } + constructor(readonly request: HttpRequest) { + super(); + } } /** @@ -16,62 +16,55 @@ class EXPECTED_REQUEST_SERIALIZATION_ERROR extends Error { * request. The thrown exception contains the serialized request. */ class RequestSerializationTestHandler implements HttpHandler { - handle( - request: HttpRequest, - options?: HttpHandlerOptions - ): Promise<{ response: HttpResponse }> { - return Promise.reject(new EXPECTED_REQUEST_SERIALIZATION_ERROR(request)); - } - updateHttpClientConfig(key: never, value: never): void {} - httpHandlerConfigs() { return {}; } + handle(request: HttpRequest, options?: HttpHandlerOptions): Promise<{ response: HttpResponse }> { + return Promise.reject(new EXPECTED_REQUEST_SERIALIZATION_ERROR(request)); + } + updateHttpClientConfig(key: never, value: never): void {} + httpHandlerConfigs() { + return {}; + } } /** * Returns a resolved Promise of the specified response contents. */ class ResponseDeserializationTestHandler implements HttpHandler { - isSuccess: boolean; - code: number; - headers: HeaderBag; - body: String; - - constructor( - isSuccess: boolean, - code: number, - headers?: HeaderBag, - body?: String - ) { - this.isSuccess = isSuccess; - this.code = code; - if (headers === undefined) { - this.headers = {}; - } else { - this.headers = headers; - } - if (body === undefined) { - body = ""; - } - this.body = body; + isSuccess: boolean; + code: number; + headers: HeaderBag; + body: String; + + constructor(isSuccess: boolean, code: number, headers?: HeaderBag, body?: String) { + this.isSuccess = isSuccess; + this.code = code; + if (headers === undefined) { + this.headers = {}; + } else { + this.headers = headers; } - - handle( - request: HttpRequest, - options?: HttpHandlerOptions - ): Promise<{ response: HttpResponse }> { - return Promise.resolve({ - response: new HttpResponse({ - statusCode: this.code, - headers: this.headers, - body: Readable.from([this.body]) - }) - }); + if (body === undefined) { + body = ""; } - updateHttpClientConfig(key: never, value: never): void {} - httpHandlerConfigs() { return {}; } + this.body = body; + } + + handle(request: HttpRequest, options?: HttpHandlerOptions): Promise<{ response: HttpResponse }> { + return Promise.resolve({ + response: new HttpResponse({ + statusCode: this.code, + headers: this.headers, + body: Readable.from([this.body]), + }), + }); + } + updateHttpClientConfig(key: never, value: never): void {} + httpHandlerConfigs() { + return {}; + } } interface comparableParts { - [ key: string ]: string + [key: string]: string; } /** @@ -79,7 +72,7 @@ interface comparableParts { */ const compareParts = (expectedParts: comparableParts, generatedParts: comparableParts) => { const unequalParts: any = {}; - Object.keys(expectedParts).forEach(key => { + Object.keys(expectedParts).forEach((key) => { if (generatedParts[key] === undefined) { unequalParts[key] = { exp: expectedParts[key], gen: undefined }; } else if (!equivalentContents(expectedParts[key], generatedParts[key])) { @@ -87,7 +80,7 @@ const compareParts = (expectedParts: comparableParts, generatedParts: comparable } }); - Object.keys(generatedParts).forEach(key => { + Object.keys(generatedParts).forEach((key) => { if (expectedParts[key] === undefined) { unequalParts[key] = { exp: undefined, gen: generatedParts[key] }; } @@ -115,10 +108,10 @@ const equivalentContents = (expected: any, generated: any): boolean => { // If a test fails with an issue in the below 6 lines, it's likely // due to an issue in the nestedness or existence of the property // being compared. - delete localExpected['$$metadata']; - delete generated['$$metadata']; - Object.keys(localExpected).forEach(key => localExpected[key] === undefined && delete localExpected[key]) - Object.keys(generated).forEach(key => generated[key] === undefined && delete generated[key]) + delete localExpected["$$metadata"]; + delete generated["$$metadata"]; + Object.keys(localExpected).forEach((key) => localExpected[key] === undefined && delete localExpected[key]); + Object.keys(generated).forEach((key) => generated[key] === undefined && delete generated[key]); const expectedProperties = Object.getOwnPropertyNames(localExpected); const generatedProperties = Object.getOwnPropertyNames(generated); @@ -137,17 +130,18 @@ const equivalentContents = (expected: any, generated: any): boolean => { } return true; -} +}; const clientParams = { region: "us-west-2", - credentials: { accessKeyId: "key", secretAccessKey: "secret" } -} + endpoint: "https://localhost/", + credentials: { accessKeyId: "key", secretAccessKey: "secret" }, +}; /** * A wrapper function that shadows `fail` from jest-jasmine2 * (jasmine2 was replaced with circus in > v27 as the default test runner) */ const fail = (error?: any): never => { - throw new Error(error); -} + throw new Error(error); +}; diff --git a/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/integration/AddHttpApiKeyAuthPluginTest.java b/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/integration/AddHttpApiKeyAuthPluginTest.java index 231936fc864..c28295d8708 100644 --- a/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/integration/AddHttpApiKeyAuthPluginTest.java +++ b/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/integration/AddHttpApiKeyAuthPluginTest.java @@ -32,20 +32,20 @@ public class AddHttpApiKeyAuthPluginTest { @Test public void httpApiKeyAuthClientOnService() { testInjects("http-api-key-auth-trait.smithy", - ", { in: 'header', name: 'Authorization', scheme: 'ApiKey' }"); + "in: 'header', name: 'Authorization', scheme: 'ApiKey'"); } @Test public void httpApiKeyAuthClientOnOperation() { testInjects("http-api-key-auth-trait-on-operation.smithy", - ", { in: 'header', name: 'Authorization', scheme: 'ApiKey' }"); + "in: 'header', name: 'Authorization', scheme: 'ApiKey'"); } // This should be identical to the httpApiKeyAuthClient test except for the parameters provided // to the middleware. @Test public void httpApiKeyAuthClientNoScheme() { - testInjects("http-api-key-auth-trait-no-scheme.smithy", ", { in: 'header', name: 'Authorization' }"); + testInjects("http-api-key-auth-trait-no-scheme.smithy", "in: 'header', name: 'Authorization'"); } private void testInjects(String filename, String extra) { @@ -60,8 +60,10 @@ private void testInjects(String filename, String extra) { // Ensure that the GetFoo operation imports the middleware and uses it with all the options. assertThat(manifest.getFileString(CodegenUtils.SOURCE_FOLDER + "/commands/GetFooCommand.ts").get(), containsString("from \"../middleware/HttpApiKeyAuth\"")); - assertThat(manifest.getFileString(CodegenUtils.SOURCE_FOLDER + "/commands/GetFooCommand.ts").get(), - containsString("getHttpApiKeyAuthPlugin(config" + extra + ")")); + + String generatedGetFooCommand = manifest.getFileString(CodegenUtils.SOURCE_FOLDER + "/commands/GetFooCommand.ts").get(); + assertThat(generatedGetFooCommand, containsString("getHttpApiKeyAuthPlugin(config")); + assertThat(generatedGetFooCommand, containsString(extra)); // Ensure that the GetBar operation does not import the middleware or use it. assertThat(manifest.getFileString(CodegenUtils.SOURCE_FOLDER + "/commands/GetBarCommand.ts").get(), @@ -93,6 +95,7 @@ private MockManifest generate(String filename) .withMember("service", Node.from("smithy.example#Example")) .withMember("package", Node.from("example")) .withMember("packageVersion", Node.from("1.0.0")) + .withMember("useLegacyAuth", Node.from(true)) .build()) .build(); diff --git a/smithy-typescript-ssdk-codegen-test-utils/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration b/smithy-typescript-ssdk-codegen-test-utils/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration index 3bfe4fdfed0..ac9c21fa612 100644 --- a/smithy-typescript-ssdk-codegen-test-utils/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration +++ b/smithy-typescript-ssdk-codegen-test-utils/src/main/resources/META-INF/services/software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration @@ -1,2 +1 @@ -software.amazon.smithy.typescript.ssdk.codegen.test.utils.AddBuiltinPlugins software.amazon.smithy.typescript.ssdk.codegen.test.utils.AddProtocols