From 8f716a58616f5710f313a34baf87156cef621a93 Mon Sep 17 00:00:00 2001 From: Chase Coalwell <782571+srchase@users.noreply.github.com> Date: Wed, 5 Jul 2023 14:06:24 -0600 Subject: [PATCH] chore(packages): re-export from migrated packages --- packages/abort-controller/jest.config.js | 5 - packages/abort-controller/package.json | 4 +- .../src/AbortController.spec.ts | 16 - .../abort-controller/src/AbortController.ts | 19 - .../abort-controller/src/AbortSignal.spec.ts | 34 - packages/abort-controller/src/AbortSignal.ts | 36 - packages/abort-controller/src/index.ts | 10 +- .../chunked-blob-reader-native/jest.config.js | 6 - .../chunked-blob-reader-native/package.json | 4 +- .../src/index.spec.ts | 46 - .../chunked-blob-reader-native/src/index.ts | 44 +- packages/chunked-blob-reader/jest.config.js | 6 - packages/chunked-blob-reader/package.json | 3 +- .../chunked-blob-reader/src/index.spec.ts | 46 - packages/chunked-blob-reader/src/index.ts | 38 +- packages/config-resolver/jest.config.js | 5 - packages/config-resolver/package.json | 6 +- ...eUseDualstackEndpointConfigOptions.spec.ts | 51 -- .../NodeUseDualstackEndpointConfigOptions.ts | 25 - .../NodeUseFipsEndpointConfigOptions.spec.ts | 51 -- .../NodeUseFipsEndpointConfigOptions.ts | 25 - .../src/endpointsConfig/index.ts | 16 - .../resolveCustomEndpointsConfig.spec.ts | 77 -- .../resolveCustomEndpointsConfig.ts | 45 - .../resolveEndpointsConfig.spec.ts | 106 --- .../endpointsConfig/resolveEndpointsConfig.ts | 75 -- .../utils/getEndpointFromRegion.spec.ts | 113 --- .../utils/getEndpointFromRegion.ts | 29 - packages/config-resolver/src/index.ts | 13 +- .../src/regionConfig/config.spec.ts | 38 - .../src/regionConfig/config.ts | 28 - .../src/regionConfig/getRealRegion.spec.ts | 36 - .../src/regionConfig/getRealRegion.ts | 11 - .../config-resolver/src/regionConfig/index.ts | 8 - .../src/regionConfig/isFipsRegion.spec.ts | 15 - .../src/regionConfig/isFipsRegion.ts | 5 - .../regionConfig/resolveRegionConfig.spec.ts | 93 -- .../src/regionConfig/resolveRegionConfig.ts | 65 -- .../src/regionInfo/EndpointVariant.ts | 11 - .../src/regionInfo/EndpointVariantTag.ts | 7 - .../src/regionInfo/PartitionHash.ts | 18 - .../src/regionInfo/RegionHash.ts | 16 - .../getHostnameFromVariants.spec.ts | 55 -- .../src/regionInfo/getHostnameFromVariants.ts | 20 - .../src/regionInfo/getRegionInfo.spec.ts | 251 ------ .../src/regionInfo/getRegionInfo.ts | 61 -- .../regionInfo/getResolvedHostname.spec.ts | 30 - .../src/regionInfo/getResolvedHostname.ts | 20 - .../regionInfo/getResolvedPartition.spec.ts | 35 - .../src/regionInfo/getResolvedPartition.ts | 14 - .../getResolvedSigningRegion.spec.ts | 55 -- .../regionInfo/getResolvedSigningRegion.ts | 26 - .../config-resolver/src/regionInfo/index.ts | 12 - .../credential-provider-imds/jest.config.js | 5 - .../credential-provider-imds/package.json | 7 +- .../src/config/Endpoint.ts | 7 - .../src/config/EndpointConfigOptions.spec.ts | 22 - .../src/config/EndpointConfigOptions.ts | 19 - .../src/config/EndpointMode.ts | 7 - .../config/EndpointModeConfigOptions.spec.ts | 27 - .../src/config/EndpointModeConfigOptions.ts | 21 - .../src/fromContainerMetadata.spec.ts | 168 ---- .../src/fromContainerMetadata.ts | 104 --- .../src/fromInstanceMetadata.spec.ts | 288 ------ .../src/fromInstanceMetadata.ts | 116 --- .../credential-provider-imds/src/index.ts | 25 +- .../remoteProvider/ImdsCredentials.spec.ts | 48 - .../src/remoteProvider/ImdsCredentials.ts | 32 - .../remoteProvider/RemoteProviderInit.spec.ts | 20 - .../src/remoteProvider/RemoteProviderInit.ts | 43 - .../src/remoteProvider/httpRequest.spec.ts | 120 --- .../src/remoteProvider/httpRequest.ts | 49 - .../src/remoteProvider/index.ts | 8 - .../src/remoteProvider/retry.spec.ts | 34 - .../src/remoteProvider/retry.ts | 18 - .../credential-provider-imds/src/types.ts | 8 - ...xtendedInstanceMetadataCredentials.spec.ts | 40 - .../getExtendedInstanceMetadataCredentials.ts | 31 - .../utils/getInstanceMetadataEndpoint.spec.ts | 107 --- .../src/utils/getInstanceMetadataEndpoint.ts | 48 - .../src/utils/staticStabilityProvider.spec.ts | 99 -- .../src/utils/staticStabilityProvider.ts | 45 - packages/eventstream-codec/jest.config.js | 5 - packages/eventstream-codec/package.json | 6 +- .../scripts/buildTestVectorsFixture.js | 80 -- .../src/EventStreamCodec.spec.ts | 20 - .../eventstream-codec/src/EventStreamCodec.ts | 110 --- .../src/HeaderMarshaller.spec.ts | 242 ----- .../eventstream-codec/src/HeaderMarshaller.ts | 199 ---- packages/eventstream-codec/src/Int64.spec.ts | 53 -- packages/eventstream-codec/src/Int64.ts | 65 -- packages/eventstream-codec/src/Message.ts | 36 - .../src/MessageDecoderStream.spec.ts | 43 - .../src/MessageDecoderStream.ts | 27 - .../src/MessageEncoderStream.spec.ts | 73 -- .../src/MessageEncoderStream.ts | 31 - .../src/SmithyMessageDecoderStream.spec.ts | 38 - .../src/SmithyMessageDecoderStream.ts | 28 - .../src/SmithyMessageEncoderStream.spec.ts | 37 - .../src/SmithyMessageEncoderStream.ts | 27 - .../src/TestVectors.fixture.ts | 148 --- packages/eventstream-codec/src/index.ts | 9 +- .../src/splitMessage.spec.ts | 46 - .../eventstream-codec/src/splitMessage.ts | 64 -- .../src/vectorTypes.fixture.ts | 16 - .../decoded/negative/corrupted_header_len | 1 - .../decoded/negative/corrupted_headers | 1 - .../decoded/negative/corrupted_length | 1 - .../decoded/negative/corrupted_payload | 1 - .../test_vectors/decoded/positive/all_headers | 58 -- .../decoded/positive/empty_message | 8 - .../decoded/positive/int32_header | 13 - .../decoded/positive/payload_no_headers | 8 - .../decoded/positive/payload_one_str_header | 13 - .../encoded/negative/corrupted_header_len | Bin 61 -> 0 bytes .../encoded/negative/corrupted_headers | Bin 61 -> 0 bytes .../encoded/negative/corrupted_length | Bin 61 -> 0 bytes .../encoded/negative/corrupted_payload | Bin 29 -> 0 bytes .../test_vectors/encoded/positive/all_headers | Bin 204 -> 0 bytes .../encoded/positive/empty_message | Bin 16 -> 0 bytes .../encoded/positive/int32_header | Bin 45 -> 0 bytes .../encoded/positive/payload_no_headers | Bin 29 -> 0 bytes .../encoded/positive/payload_one_str_header | Bin 61 -> 0 bytes .../eventstream-serde-browser/package.json | 3 +- .../src/EventStreamMarshaller.ts | 71 -- .../eventstream-serde-browser/src/index.ts | 13 +- .../eventstream-serde-browser/src/provider.ts | 10 - .../eventstream-serde-browser/src/utils.ts | 38 - .../jest.config.js | 5 - .../package.json | 4 +- .../src/EventStreamSerdeConfig.spec.ts | 19 - .../src/EventStreamSerdeConfig.ts | 31 - .../src/index.ts | 5 +- .../eventstream-serde-node/jest.config.js | 5 - packages/eventstream-serde-node/package.json | 3 +- .../src/EventStreamMarshaller.ts | 43 - packages/eventstream-serde-node/src/index.ts | 9 +- .../eventstream-serde-node/src/provider.ts | 10 - packages/eventstream-serde-node/src/utils.ts | 44 - .../jest.config.js | 5 - .../eventstream-serde-universal/package.json | 5 +- .../src/EventStreamMarshaller.ts | 58 -- .../MockEventMessageSource.fixture.ts | 49 - .../src/fixtures/event.fixture.ts | 31 - .../src/getChunkedStream.spec.ts | 100 -- .../src/getChunkedStream.ts | 98 -- .../src/getUnmarshalledStream.spec.ts | 142 --- .../src/getUnmarshalledStream.ts | 71 -- .../eventstream-serde-universal/src/index.ts | 9 +- .../src/provider.ts | 10 - packages/fetch-http-handler/jest.config.js | 7 - packages/fetch-http-handler/karma.conf.js | 32 - packages/fetch-http-handler/package.json | 7 +- .../src/fetch-http-handler.browser.spec.ts | 11 - .../src/fetch-http-handler.spec.ts | 341 ------- .../src/fetch-http-handler.ts | 133 --- packages/fetch-http-handler/src/index.spec.ts | 7 - packages/fetch-http-handler/src/index.ts | 3 +- .../fetch-http-handler/src/request-timeout.ts | 11 - .../src/stream-collector.browser.spec.ts | 40 - .../src/stream-collector.spec.ts | 36 - .../src/stream-collector.ts | 57 -- packages/hash-blob-browser/jest.config.js | 6 - packages/hash-blob-browser/karma.conf.js | 26 - packages/hash-blob-browser/package.json | 6 +- packages/hash-blob-browser/src/index.spec.ts | 15 - packages/hash-blob-browser/src/index.ts | 19 +- packages/hash-node/jest.config.js | 5 - packages/hash-node/package.json | 6 +- packages/hash-node/src/index.spec.ts | 52 -- packages/hash-node/src/index.ts | 51 +- packages/hash-stream-node/jest.config.js | 5 - packages/hash-stream-node/package.json | 5 +- .../src/HashCalculator.spec.ts | 58 -- .../hash-stream-node/src/HashCalculator.ts | 21 - .../src/fileStreamHasher.spec.ts | 49 - .../hash-stream-node/src/fileStreamHasher.ts | 38 - packages/hash-stream-node/src/index.ts | 9 +- .../src/readableStreamHasher.spec.ts | 152 ---- .../src/readableStreamHasher.ts | 30 - packages/invalid-dependency/jest.config.js | 5 - packages/invalid-dependency/package.json | 4 +- packages/invalid-dependency/src/index.ts | 9 +- .../src/invalidFunction.spec.ts | 8 - .../invalid-dependency/src/invalidFunction.ts | 6 - .../src/invalidProvider.spec.ts | 10 - .../invalid-dependency/src/invalidProvider.ts | 5 - packages/is-array-buffer/jest.config.js | 5 - packages/is-array-buffer/package.json | 3 +- packages/is-array-buffer/src/index.spec.ts | 34 - packages/is-array-buffer/src/index.ts | 7 +- packages/md5-js/jest.config.js | 5 - packages/md5-js/package.json | 5 +- packages/md5-js/src/constants.ts | 14 - packages/md5-js/src/index.spec.ts | 17 - packages/md5-js/src/index.ts | 209 +---- .../jest.config.js | 5 - .../package.json | 6 +- .../applyMd5BodyChecksumMiddleware.spec.ts | 101 --- .../src/applyMd5BodyChecksumMiddleware.ts | 70 -- .../src/index.spec.ts | 7 - .../src/index.ts | 3 +- .../src/md5Configuration.ts | 30 - .../middleware-content-length/jest.config.js | 5 - .../middleware-content-length/package.json | 3 +- .../middleware-content-length/src/index.ts | 59 +- .../middleware-endpoint/jest.config.integ.js | 4 - packages/middleware-endpoint/jest.config.js | 5 - packages/middleware-endpoint/package.json | 8 +- .../createConfigValueProvider.spec.ts | 38 - .../src/adaptors/createConfigValueProvider.ts | 44 - .../adaptors/getEndpointFromInstructions.ts | 90 -- .../middleware-endpoint/src/adaptors/index.ts | 8 - .../src/adaptors/toEndpointV1.ts | 17 - .../src/endpointMiddleware.ts | 56 -- .../src/getEndpointPlugin.ts | 36 - packages/middleware-endpoint/src/index.ts | 21 +- .../src/resolveEndpointConfig.ts | 116 --- .../src/service-customizations/index.ts | 4 - .../src/service-customizations/s3.ts | 69 -- packages/middleware-endpoint/src/types.ts | 42 - packages/middleware-retry/jest.config.js | 5 - packages/middleware-retry/package.json | 8 +- .../src/AdaptiveRetryStrategy.spec.ts | 97 -- .../src/AdaptiveRetryStrategy.ts | 39 - .../src/StandardRetryStrategy.spec.ts | 605 ------------- .../src/StandardRetryStrategy.ts | 144 --- .../src/configurations.spec.ts | 171 ---- .../middleware-retry/src/configurations.ts | 93 -- .../src/defaultRetryQuota.spec.ts | 152 ---- .../middleware-retry/src/defaultRetryQuota.ts | 57 -- .../middleware-retry/src/delayDecider.spec.ts | 50 - packages/middleware-retry/src/delayDecider.ts | 7 - packages/middleware-retry/src/index.ts | 8 +- .../src/omitRetryHeadersMiddleware.spec.ts | 49 - .../src/omitRetryHeadersMiddleware.ts | 36 - .../middleware-retry/src/retryDecider.spec.ts | 78 -- packages/middleware-retry/src/retryDecider.ts | 15 - .../src/retryMiddleware.spec.ts | 418 --------- .../middleware-retry/src/retryMiddleware.ts | 132 --- packages/middleware-retry/src/types.ts | 60 -- packages/middleware-retry/src/util.ts | 8 - packages/middleware-serde/jest.config.js | 5 - packages/middleware-serde/package.json | 4 +- .../src/deserializerMiddleware.spec.ts | 93 -- .../src/deserializerMiddleware.ts | 38 - packages/middleware-serde/src/index.ts | 4 +- packages/middleware-serde/src/serdePlugin.ts | 53 -- .../src/serializerMiddleware.spec.ts | 62 -- .../src/serializerMiddleware.ts | 35 - .../middleware-stack/jest.config.integ.js | 4 - packages/middleware-stack/jest.config.js | 5 - packages/middleware-stack/package.json | 4 +- .../src/MiddlewareStack.spec.ts | 395 -------- .../middleware-stack/src/MiddlewareStack.ts | 277 ------ packages/middleware-stack/src/index.ts | 2 +- packages/middleware-stack/src/types.ts | 38 - packages/node-config-provider/jest.config.js | 5 - packages/node-config-provider/package.json | 6 +- .../src/configLoader.spec.ts | 64 -- .../node-config-provider/src/configLoader.ts | 37 - .../node-config-provider/src/fromEnv.spec.ts | 49 - packages/node-config-provider/src/fromEnv.ts | 25 - .../src/fromSharedConfigFiles.spec.ts | 173 ---- .../src/fromSharedConfigFiles.ts | 47 - .../src/fromStatic.spec.ts | 30 - .../node-config-provider/src/fromStatic.ts | 9 - packages/node-config-provider/src/index.ts | 2 +- .../fixtures/test-server-cert.pem | 26 - .../fixtures/test-server-key.pem | 27 - packages/node-http-handler/jest.config.js | 5 - packages/node-http-handler/package.json | 7 +- packages/node-http-handler/src/constants.ts | 5 - .../src/get-transformed-headers.ts | 15 - packages/node-http-handler/src/index.spec.ts | 7 - packages/node-http-handler/src/index.ts | 4 +- .../src/node-http-handler.spec.ts | 643 ------------- .../src/node-http-handler.ts | 195 ---- .../src/node-http2-connection-manager.ts | 125 --- .../src/node-http2-connection-pool.ts | 42 - .../src/node-http2-handler.spec.ts | 646 ------------- .../src/node-http2-handler.ts | 214 ----- .../node-http-handler/src/readable.mock.ts | 29 - packages/node-http-handler/src/server.mock.ts | 63 -- .../src/set-connection-timeout.spec.ts | 96 -- .../src/set-connection-timeout.ts | 28 - .../src/set-socket-keep-alive.spec.ts | 46 - .../src/set-socket-keep-alive.ts | 16 - .../src/set-socket-timeout.spec.ts | 50 - .../src/set-socket-timeout.ts | 9 - .../src/stream-collector/collector.spec.ts | 31 - .../src/stream-collector/collector.ts | 8 - .../src/stream-collector/index.spec.ts | 28 - .../src/stream-collector/index.ts | 20 - .../src/stream-collector/readable.mock.ts | 30 - .../src/write-request-body.spec.ts | 30 - .../src/write-request-body.ts | 66 -- packages/property-provider/jest.config.js | 5 - packages/property-provider/package.json | 2 +- .../src/CredentialsProviderError.spec.ts | 18 - .../src/CredentialsProviderError.ts | 1 - .../src/ProviderError.spec.ts | 35 - .../property-provider/src/ProviderError.ts | 1 - .../src/TokenProviderError.spec.ts | 18 - .../src/TokenProviderError.ts | 1 - packages/property-provider/src/chain.spec.ts | 117 --- packages/property-provider/src/chain.ts | 1 - .../property-provider/src/fromStatic.spec.ts | 18 - packages/property-provider/src/fromStatic.ts | 1 - packages/property-provider/src/index.ts | 7 +- .../property-provider/src/memoize.spec.ts | 224 ----- packages/property-provider/src/memoize.ts | 1 - packages/protocol-http/jest.config.js | 5 - packages/protocol-http/package.json | 4 +- packages/protocol-http/src/Field.ts | 78 -- packages/protocol-http/src/FieldPosition.ts | 4 - packages/protocol-http/src/Fields.ts | 59 -- packages/protocol-http/src/httpHandler.ts | 6 - packages/protocol-http/src/httpRequest.ts | 70 -- packages/protocol-http/src/httpResponse.ts | 29 - packages/protocol-http/src/index.ts | 8 +- .../protocol-http/src/isValidHostname.spec.ts | 17 - packages/protocol-http/src/isValidHostname.ts | 4 - packages/querystring-builder/jest.config.js | 5 - packages/querystring-builder/package.json | 3 +- packages/querystring-builder/src/index.ts | 27 +- packages/querystring-parser/jest.config.js | 5 - packages/querystring-parser/package.json | 4 +- packages/querystring-parser/src/index.spec.ts | 25 - packages/querystring-parser/src/index.ts | 29 +- .../package.json | 1 + .../src/index.ts | 18 +- .../src/sdk-client-toc-plugin.ts | 182 ---- .../src/utils.ts | 8 - .../jest.config.js | 5 - .../service-error-classification/package.json | 5 +- .../src/constants.ts | 52 -- .../src/index.spec.ts | 142 --- .../service-error-classification/src/index.ts | 41 +- .../shared-ini-file-loader/jest.config.js | 5 - packages/shared-ini-file-loader/package.json | 4 +- .../src/getConfigFilepath.spec.ts | 39 - .../src/getConfigFilepath.ts | 7 - .../src/getCredentialsFilepath.spec.ts | 39 - .../src/getCredentialsFilepath.ts | 8 - .../src/getHomeDir.spec.ts | 62 -- .../shared-ini-file-loader/src/getHomeDir.ts | 17 - .../src/getProfileData.spec.ts | 67 -- .../src/getProfileData.ts | 18 - .../src/getProfileName.spec.ts | 31 - .../src/getProfileName.ts | 5 - .../src/getSSOTokenFilepath.spec.ts | 78 -- .../src/getSSOTokenFilepath.ts | 13 - .../src/getSSOTokenFromFile.spec.ts | 74 -- .../src/getSSOTokenFromFile.ts | 63 -- .../src/getSsoSessionData.spec.ts | 55 -- .../src/getSsoSessionData.ts | 14 - packages/shared-ini-file-loader/src/index.ts | 9 +- .../src/loadSharedConfigFiles.spec.ts | 74 -- .../src/loadSharedConfigFiles.ts | 54 -- .../src/loadSsoSessionData.spec.ts | 60 -- .../src/loadSsoSessionData.ts | 23 - .../src/mergeConfigFiles.spec.ts | 25 - .../src/mergeConfigFiles.ts | 20 - .../src/parseIni.spec.ts | 67 -- .../shared-ini-file-loader/src/parseIni.ts | 35 - .../src/parseKnownFiles.spec.ts | 44 - .../src/parseKnownFiles.ts | 22 - .../src/slurpFile.spec.ts | 121 --- .../shared-ini-file-loader/src/slurpFile.ts | 17 - packages/shared-ini-file-loader/src/types.ts | 20 - packages/signature-v4/jest.config.js | 7 - packages/signature-v4/package.json | 10 +- packages/signature-v4/src/SignatureV4.spec.ts | 851 ------------------ packages/signature-v4/src/SignatureV4.ts | 387 -------- .../signature-v4/src/cloneRequest.spec.ts | 58 -- packages/signature-v4/src/cloneRequest.ts | 19 - packages/signature-v4/src/constants.ts | 53 -- .../src/credentialDerivation.spec.ts | 76 -- .../signature-v4/src/credentialDerivation.ts | 75 -- .../src/getCanonicalHeaders.spec.ts | 112 --- .../signature-v4/src/getCanonicalHeaders.ts | 35 - .../src/getCanonicalQuery.spec.ts | 109 --- .../signature-v4/src/getCanonicalQuery.ts | 37 - .../signature-v4/src/getPayloadHash.spec.ts | 90 -- packages/signature-v4/src/getPayloadHash.ts | 33 - packages/signature-v4/src/headerUtil.ts | 34 - packages/signature-v4/src/index.ts | 8 +- .../src/moveHeadersToQuery.spec.ts | 102 --- .../signature-v4/src/moveHeadersToQuery.ts | 27 - .../signature-v4/src/prepareRequest.spec.ts | 38 - packages/signature-v4/src/prepareRequest.ts | 20 - packages/signature-v4/src/suite.fixture.ts | 435 --------- packages/signature-v4/src/suite.spec.ts | 29 - packages/signature-v4/src/utilDate.spec.ts | 43 - packages/signature-v4/src/utilDate.ts | 19 - packages/smithy-client/.eslintrc.json | 6 - packages/smithy-client/jest.config.js | 5 - packages/smithy-client/package.json | 7 +- packages/smithy-client/src/NoOpLogger.ts | 12 - packages/smithy-client/src/client.spec.ts | 53 -- packages/smithy-client/src/client.ts | 77 -- .../src/collect-stream-body.spec.ts | 49 - .../smithy-client/src/collect-stream-body.ts | 26 - packages/smithy-client/src/command.ts | 22 - packages/smithy-client/src/constants.ts | 4 - .../src/create-aggregated-client.spec.ts | 61 -- .../src/create-aggregated-client.ts | 30 - packages/smithy-client/src/date-utils.spec.ts | 314 ------- packages/smithy-client/src/date-utils.ts | 392 -------- .../src/default-error-handler.ts | 39 - packages/smithy-client/src/defaults-mode.ts | 57 -- .../emitWarningIfUnsupportedVersion.spec.ts | 69 -- .../src/emitWarningIfUnsupportedVersion.ts | 24 - packages/smithy-client/src/exceptions.spec.ts | 65 -- packages/smithy-client/src/exceptions.ts | 70 -- .../src/extended-encode-uri-component.spec.ts | 20 - .../src/extended-encode-uri-component.ts | 11 - .../src/get-array-if-single-item.ts | 8 - .../src/get-value-from-text-node.spec.ts | 52 -- .../src/get-value-from-text-node.ts | 17 - packages/smithy-client/src/index.ts | 23 +- packages/smithy-client/src/lazy-json.spec.ts | 36 - packages/smithy-client/src/lazy-json.ts | 57 -- .../smithy-client/src/object-mapping.spec.ts | 234 ----- packages/smithy-client/src/object-mapping.ts | 323 ------- .../smithy-client/src/parse-utils.spec.ts | 790 ---------------- packages/smithy-client/src/parse-utils.ts | 558 ------------ packages/smithy-client/src/resolve-path.ts | 32 - packages/smithy-client/src/ser-utils.spec.ts | 14 - packages/smithy-client/src/ser-utils.ts | 22 - packages/smithy-client/src/serde-json.spec.ts | 39 - packages/smithy-client/src/serde-json.ts | 28 - .../smithy-client/src/split-every.spec.ts | 60 -- packages/smithy-client/src/split-every.ts | 48 - packages/url-parser/jest.config.js | 5 - packages/url-parser/package.json | 5 +- packages/url-parser/src/index.spec.ts | 47 - packages/url-parser/src/index.ts | 26 +- packages/util-base64/jest.config.js | 6 - packages/util-base64/package.json | 4 +- .../util-base64/src/__mocks__/testCases.json | 10 - packages/util-base64/src/constants.browser.ts | 34 - .../src/fromBase64.browser.spec.ts | 21 - .../util-base64/src/fromBase64.browser.ts | 46 - packages/util-base64/src/fromBase64.spec.ts | 18 - packages/util-base64/src/fromBase64.ts | 29 - packages/util-base64/src/index.ts | 3 +- .../util-base64/src/toBase64.browser.spec.ts | 11 - packages/util-base64/src/toBase64.browser.ts | 30 - packages/util-base64/src/toBase64.spec.ts | 12 - packages/util-base64/src/toBase64.ts | 10 - .../util-body-length-browser/jest.config.js | 6 - .../util-body-length-browser/package.json | 3 +- .../src/calculateBodyLength.spec.ts | 51 -- .../src/calculateBodyLength.ts | 24 - .../util-body-length-browser/src/index.ts | 5 +- packages/util-body-length-node/jest.config.js | 5 - packages/util-body-length-node/package.json | 3 +- .../src/calculateBodyLength.spec.ts | 70 -- .../src/calculateBodyLength.ts | 25 - packages/util-body-length-node/src/index.ts | 5 +- packages/util-buffer-from/jest.config.js | 5 - packages/util-buffer-from/package.json | 4 +- packages/util-buffer-from/src/index.spec.ts | 71 -- packages/util-buffer-from/src/index.ts | 30 +- packages/util-config-provider/jest.config.js | 5 - packages/util-config-provider/package.json | 3 +- .../src/booleanSelector.spec.ts | 31 - .../src/booleanSelector.ts | 19 - packages/util-config-provider/src/index.ts | 2 +- .../util-defaults-mode-browser/jest.config.js | 5 - .../util-defaults-mode-browser/package.json | 5 +- .../src/constants.ts | 14 - .../util-defaults-mode-browser/src/index.ts | 5 +- .../resolveDefaultsModeConfig.native.spec.ts | 26 - .../src/resolveDefaultsModeConfig.native.ts | 44 - .../src/resolveDefaultsModeConfig.spec.ts | 57 -- .../src/resolveDefaultsModeConfig.ts | 54 -- .../util-defaults-mode-node/jest.config.js | 5 - packages/util-defaults-mode-node/package.json | 8 +- .../util-defaults-mode-node/src/constants.ts | 24 - .../src/defaultsModeConfig.ts | 18 - packages/util-defaults-mode-node/src/index.ts | 5 +- .../src/resolveDefaultsModeConfig.spec.ts | 113 --- .../src/resolveDefaultsModeConfig.ts | 91 -- packages/util-hex-encoding/jest.config.js | 5 - packages/util-hex-encoding/package.json | 3 +- packages/util-hex-encoding/src/index.spec.ts | 29 - packages/util-hex-encoding/src/index.ts | 50 +- packages/util-middleware/jest.config.js | 5 - packages/util-middleware/package.json | 3 +- packages/util-middleware/src/index.ts | 5 +- .../src/normalizeProvider.spec.ts | 22 - .../util-middleware/src/normalizeProvider.ts | 12 - packages/util-retry/jest.config.js | 5 - packages/util-retry/package.json | 4 +- .../src/AdaptiveRetryStrategy.spec.ts | 102 --- .../util-retry/src/AdaptiveRetryStrategy.ts | 58 -- .../src/ConfiguredRetryStrategy.spec.ts | 17 - .../util-retry/src/ConfiguredRetryStrategy.ts | 59 -- .../util-retry/src/DefaultRateLimiter.spec.ts | 120 --- packages/util-retry/src/DefaultRateLimiter.ts | 156 ---- .../src/StandardRetryStrategy.spec.ts | 136 --- .../util-retry/src/StandardRetryStrategy.ts | 105 --- packages/util-retry/src/config.ts | 22 - packages/util-retry/src/constants.ts | 67 -- .../src/defaultRetryBackoffStrategy.spec.ts | 67 -- .../src/defaultRetryBackoffStrategy.ts | 23 - .../util-retry/src/defaultRetryToken.spec.ts | 56 -- packages/util-retry/src/defaultRetryToken.ts | 26 - packages/util-retry/src/index.ts | 8 +- packages/util-retry/src/types.ts | 20 - packages/util-stream-browser/jest.config.js | 5 - packages/util-stream-browser/package.json | 8 +- .../src/getAwsChunkedEncodingStream.ts | 40 - packages/util-stream-browser/src/index.ts | 9 +- .../src/sdk-stream-mixin.spec.ts | 222 ----- .../src/sdk-stream-mixin.ts | 79 -- packages/util-stream-node/jest.config.js | 5 - packages/util-stream-node/package.json | 6 +- .../src/getAwsChunkedEncodingStream.spec.ts | 109 --- .../src/getAwsChunkedEncodingStream.ts | 37 - packages/util-stream-node/src/index.ts | 9 +- .../src/sdk-stream-mixin.spec.ts | 184 ---- .../util-stream-node/src/sdk-stream-mixin.ts | 60 -- packages/util-stream/jest.config.integ.js | 4 - packages/util-stream/jest.config.js | 6 - packages/util-stream/karma.conf.js | 26 - packages/util-stream/package.json | 11 +- .../src/blob/Uint8ArrayBlobAdapter.spec.ts | 32 - .../src/blob/Uint8ArrayBlobAdapter.ts | 37 - packages/util-stream/src/blob/transforms.ts | 24 - ...etAwsChunkedEncodingStream.browser.spec.ts | 90 -- .../getAwsChunkedEncodingStream.browser.ts | 40 - .../src/getAwsChunkedEncodingStream.spec.ts | 109 --- .../src/getAwsChunkedEncodingStream.ts | 37 - packages/util-stream/src/index.ts | 4 +- .../src/sdk-stream-mixin.browser.spec.ts | 222 ----- .../src/sdk-stream-mixin.browser.ts | 79 -- .../util-stream/src/sdk-stream-mixin.spec.ts | 184 ---- packages/util-stream/src/sdk-stream-mixin.ts | 60 -- packages/util-uri-escape/jest.config.js | 5 - packages/util-uri-escape/package.json | 3 +- .../src/escape-uri-path.spec.ts | 11 - .../util-uri-escape/src/escape-uri-path.ts | 6 - .../util-uri-escape/src/escape-uri.spec.ts | 21 - packages/util-uri-escape/src/escape-uri.ts | 8 - packages/util-uri-escape/src/index.ts | 9 +- packages/util-utf8/jest.config.js | 6 - packages/util-utf8/package.json | 4 +- .../util-utf8/src/fromUtf8.browser.spec.ts | 16 - packages/util-utf8/src/fromUtf8.browser.ts | 1 - packages/util-utf8/src/fromUtf8.spec.ts | 31 - packages/util-utf8/src/fromUtf8.ts | 6 - packages/util-utf8/src/index.ts | 4 +- packages/util-utf8/src/toUint8Array.spec.ts | 18 - packages/util-utf8/src/toUint8Array.ts | 13 - packages/util-utf8/src/toUtf8.browser.spec.ts | 16 - packages/util-utf8/src/toUtf8.browser.ts | 1 - packages/util-utf8/src/toUtf8.spec.ts | 31 - packages/util-utf8/src/toUtf8.ts | 4 - packages/util-waiter/.eslintrc.json | 6 - packages/util-waiter/jest.config.js | 5 - packages/util-waiter/package.json | 5 +- packages/util-waiter/src/createWaiter.spec.ts | 86 -- packages/util-waiter/src/createWaiter.ts | 43 - packages/util-waiter/src/index.spec.ts | 7 - packages/util-waiter/src/index.ts | 3 +- packages/util-waiter/src/poller.spec.ts | 134 --- packages/util-waiter/src/poller.ts | 58 -- packages/util-waiter/src/utils/index.ts | 8 - packages/util-waiter/src/utils/sleep.spec.ts | 20 - packages/util-waiter/src/utils/sleep.ts | 6 - .../util-waiter/src/utils/validate.spec.ts | 91 -- packages/util-waiter/src/utils/validate.ts | 25 - packages/util-waiter/src/waiter.ts | 74 -- private/aws-middleware-test/package.json | 4 + .../src/util-stream.spec.ts | 10 +- yarn.lock | 754 ++++++++-------- 580 files changed, 556 insertions(+), 28503 deletions(-) delete mode 100644 packages/abort-controller/jest.config.js delete mode 100644 packages/abort-controller/src/AbortController.spec.ts delete mode 100644 packages/abort-controller/src/AbortController.ts delete mode 100644 packages/abort-controller/src/AbortSignal.spec.ts delete mode 100644 packages/abort-controller/src/AbortSignal.ts delete mode 100644 packages/chunked-blob-reader-native/jest.config.js delete mode 100644 packages/chunked-blob-reader-native/src/index.spec.ts delete mode 100644 packages/chunked-blob-reader/jest.config.js delete mode 100644 packages/chunked-blob-reader/src/index.spec.ts delete mode 100644 packages/config-resolver/jest.config.js delete mode 100644 packages/config-resolver/src/endpointsConfig/NodeUseDualstackEndpointConfigOptions.spec.ts delete mode 100644 packages/config-resolver/src/endpointsConfig/NodeUseDualstackEndpointConfigOptions.ts delete mode 100644 packages/config-resolver/src/endpointsConfig/NodeUseFipsEndpointConfigOptions.spec.ts delete mode 100644 packages/config-resolver/src/endpointsConfig/NodeUseFipsEndpointConfigOptions.ts delete mode 100644 packages/config-resolver/src/endpointsConfig/index.ts delete mode 100644 packages/config-resolver/src/endpointsConfig/resolveCustomEndpointsConfig.spec.ts delete mode 100644 packages/config-resolver/src/endpointsConfig/resolveCustomEndpointsConfig.ts delete mode 100644 packages/config-resolver/src/endpointsConfig/resolveEndpointsConfig.spec.ts delete mode 100644 packages/config-resolver/src/endpointsConfig/resolveEndpointsConfig.ts delete mode 100644 packages/config-resolver/src/endpointsConfig/utils/getEndpointFromRegion.spec.ts delete mode 100644 packages/config-resolver/src/endpointsConfig/utils/getEndpointFromRegion.ts delete mode 100644 packages/config-resolver/src/regionConfig/config.spec.ts delete mode 100644 packages/config-resolver/src/regionConfig/config.ts delete mode 100644 packages/config-resolver/src/regionConfig/getRealRegion.spec.ts delete mode 100644 packages/config-resolver/src/regionConfig/getRealRegion.ts delete mode 100644 packages/config-resolver/src/regionConfig/index.ts delete mode 100644 packages/config-resolver/src/regionConfig/isFipsRegion.spec.ts delete mode 100644 packages/config-resolver/src/regionConfig/isFipsRegion.ts delete mode 100644 packages/config-resolver/src/regionConfig/resolveRegionConfig.spec.ts delete mode 100644 packages/config-resolver/src/regionConfig/resolveRegionConfig.ts delete mode 100644 packages/config-resolver/src/regionInfo/EndpointVariant.ts delete mode 100644 packages/config-resolver/src/regionInfo/EndpointVariantTag.ts delete mode 100644 packages/config-resolver/src/regionInfo/PartitionHash.ts delete mode 100644 packages/config-resolver/src/regionInfo/RegionHash.ts delete mode 100644 packages/config-resolver/src/regionInfo/getHostnameFromVariants.spec.ts delete mode 100644 packages/config-resolver/src/regionInfo/getHostnameFromVariants.ts delete mode 100644 packages/config-resolver/src/regionInfo/getRegionInfo.spec.ts delete mode 100644 packages/config-resolver/src/regionInfo/getRegionInfo.ts delete mode 100644 packages/config-resolver/src/regionInfo/getResolvedHostname.spec.ts delete mode 100644 packages/config-resolver/src/regionInfo/getResolvedHostname.ts delete mode 100644 packages/config-resolver/src/regionInfo/getResolvedPartition.spec.ts delete mode 100644 packages/config-resolver/src/regionInfo/getResolvedPartition.ts delete mode 100644 packages/config-resolver/src/regionInfo/getResolvedSigningRegion.spec.ts delete mode 100644 packages/config-resolver/src/regionInfo/getResolvedSigningRegion.ts delete mode 100644 packages/config-resolver/src/regionInfo/index.ts delete mode 100644 packages/credential-provider-imds/jest.config.js delete mode 100644 packages/credential-provider-imds/src/config/Endpoint.ts delete mode 100644 packages/credential-provider-imds/src/config/EndpointConfigOptions.spec.ts delete mode 100644 packages/credential-provider-imds/src/config/EndpointConfigOptions.ts delete mode 100644 packages/credential-provider-imds/src/config/EndpointMode.ts delete mode 100644 packages/credential-provider-imds/src/config/EndpointModeConfigOptions.spec.ts delete mode 100644 packages/credential-provider-imds/src/config/EndpointModeConfigOptions.ts delete mode 100644 packages/credential-provider-imds/src/fromContainerMetadata.spec.ts delete mode 100644 packages/credential-provider-imds/src/fromContainerMetadata.ts delete mode 100644 packages/credential-provider-imds/src/fromInstanceMetadata.spec.ts delete mode 100644 packages/credential-provider-imds/src/fromInstanceMetadata.ts delete mode 100644 packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.spec.ts delete mode 100644 packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.ts delete mode 100644 packages/credential-provider-imds/src/remoteProvider/RemoteProviderInit.spec.ts delete mode 100644 packages/credential-provider-imds/src/remoteProvider/RemoteProviderInit.ts delete mode 100644 packages/credential-provider-imds/src/remoteProvider/httpRequest.spec.ts delete mode 100644 packages/credential-provider-imds/src/remoteProvider/httpRequest.ts delete mode 100644 packages/credential-provider-imds/src/remoteProvider/index.ts delete mode 100644 packages/credential-provider-imds/src/remoteProvider/retry.spec.ts delete mode 100644 packages/credential-provider-imds/src/remoteProvider/retry.ts delete mode 100644 packages/credential-provider-imds/src/types.ts delete mode 100644 packages/credential-provider-imds/src/utils/getExtendedInstanceMetadataCredentials.spec.ts delete mode 100644 packages/credential-provider-imds/src/utils/getExtendedInstanceMetadataCredentials.ts delete mode 100644 packages/credential-provider-imds/src/utils/getInstanceMetadataEndpoint.spec.ts delete mode 100644 packages/credential-provider-imds/src/utils/getInstanceMetadataEndpoint.ts delete mode 100644 packages/credential-provider-imds/src/utils/staticStabilityProvider.spec.ts delete mode 100644 packages/credential-provider-imds/src/utils/staticStabilityProvider.ts delete mode 100644 packages/eventstream-codec/jest.config.js delete mode 100644 packages/eventstream-codec/scripts/buildTestVectorsFixture.js delete mode 100644 packages/eventstream-codec/src/EventStreamCodec.spec.ts delete mode 100644 packages/eventstream-codec/src/EventStreamCodec.ts delete mode 100644 packages/eventstream-codec/src/HeaderMarshaller.spec.ts delete mode 100644 packages/eventstream-codec/src/HeaderMarshaller.ts delete mode 100644 packages/eventstream-codec/src/Int64.spec.ts delete mode 100644 packages/eventstream-codec/src/Int64.ts delete mode 100644 packages/eventstream-codec/src/Message.ts delete mode 100644 packages/eventstream-codec/src/MessageDecoderStream.spec.ts delete mode 100644 packages/eventstream-codec/src/MessageDecoderStream.ts delete mode 100644 packages/eventstream-codec/src/MessageEncoderStream.spec.ts delete mode 100644 packages/eventstream-codec/src/MessageEncoderStream.ts delete mode 100644 packages/eventstream-codec/src/SmithyMessageDecoderStream.spec.ts delete mode 100644 packages/eventstream-codec/src/SmithyMessageDecoderStream.ts delete mode 100644 packages/eventstream-codec/src/SmithyMessageEncoderStream.spec.ts delete mode 100644 packages/eventstream-codec/src/SmithyMessageEncoderStream.ts delete mode 100644 packages/eventstream-codec/src/TestVectors.fixture.ts delete mode 100644 packages/eventstream-codec/src/splitMessage.spec.ts delete mode 100644 packages/eventstream-codec/src/splitMessage.ts delete mode 100644 packages/eventstream-codec/src/vectorTypes.fixture.ts delete mode 100644 packages/eventstream-codec/test_vectors/decoded/negative/corrupted_header_len delete mode 100644 packages/eventstream-codec/test_vectors/decoded/negative/corrupted_headers delete mode 100644 packages/eventstream-codec/test_vectors/decoded/negative/corrupted_length delete mode 100644 packages/eventstream-codec/test_vectors/decoded/negative/corrupted_payload delete mode 100644 packages/eventstream-codec/test_vectors/decoded/positive/all_headers delete mode 100644 packages/eventstream-codec/test_vectors/decoded/positive/empty_message delete mode 100644 packages/eventstream-codec/test_vectors/decoded/positive/int32_header delete mode 100644 packages/eventstream-codec/test_vectors/decoded/positive/payload_no_headers delete mode 100644 packages/eventstream-codec/test_vectors/decoded/positive/payload_one_str_header delete mode 100644 packages/eventstream-codec/test_vectors/encoded/negative/corrupted_header_len delete mode 100644 packages/eventstream-codec/test_vectors/encoded/negative/corrupted_headers delete mode 100644 packages/eventstream-codec/test_vectors/encoded/negative/corrupted_length delete mode 100644 packages/eventstream-codec/test_vectors/encoded/negative/corrupted_payload delete mode 100644 packages/eventstream-codec/test_vectors/encoded/positive/all_headers delete mode 100644 packages/eventstream-codec/test_vectors/encoded/positive/empty_message delete mode 100644 packages/eventstream-codec/test_vectors/encoded/positive/int32_header delete mode 100644 packages/eventstream-codec/test_vectors/encoded/positive/payload_no_headers delete mode 100644 packages/eventstream-codec/test_vectors/encoded/positive/payload_one_str_header delete mode 100644 packages/eventstream-serde-browser/src/EventStreamMarshaller.ts delete mode 100644 packages/eventstream-serde-browser/src/provider.ts delete mode 100644 packages/eventstream-serde-browser/src/utils.ts delete mode 100644 packages/eventstream-serde-config-resolver/jest.config.js delete mode 100644 packages/eventstream-serde-config-resolver/src/EventStreamSerdeConfig.spec.ts delete mode 100644 packages/eventstream-serde-config-resolver/src/EventStreamSerdeConfig.ts delete mode 100644 packages/eventstream-serde-node/jest.config.js delete mode 100644 packages/eventstream-serde-node/src/EventStreamMarshaller.ts delete mode 100644 packages/eventstream-serde-node/src/provider.ts delete mode 100644 packages/eventstream-serde-node/src/utils.ts delete mode 100644 packages/eventstream-serde-universal/jest.config.js delete mode 100644 packages/eventstream-serde-universal/src/EventStreamMarshaller.ts delete mode 100644 packages/eventstream-serde-universal/src/fixtures/MockEventMessageSource.fixture.ts delete mode 100644 packages/eventstream-serde-universal/src/fixtures/event.fixture.ts delete mode 100644 packages/eventstream-serde-universal/src/getChunkedStream.spec.ts delete mode 100644 packages/eventstream-serde-universal/src/getChunkedStream.ts delete mode 100644 packages/eventstream-serde-universal/src/getUnmarshalledStream.spec.ts delete mode 100644 packages/eventstream-serde-universal/src/getUnmarshalledStream.ts delete mode 100644 packages/eventstream-serde-universal/src/provider.ts delete mode 100644 packages/fetch-http-handler/jest.config.js delete mode 100644 packages/fetch-http-handler/karma.conf.js delete mode 100644 packages/fetch-http-handler/src/fetch-http-handler.browser.spec.ts delete mode 100644 packages/fetch-http-handler/src/fetch-http-handler.spec.ts delete mode 100644 packages/fetch-http-handler/src/fetch-http-handler.ts delete mode 100644 packages/fetch-http-handler/src/index.spec.ts delete mode 100644 packages/fetch-http-handler/src/request-timeout.ts delete mode 100644 packages/fetch-http-handler/src/stream-collector.browser.spec.ts delete mode 100644 packages/fetch-http-handler/src/stream-collector.spec.ts delete mode 100644 packages/fetch-http-handler/src/stream-collector.ts delete mode 100644 packages/hash-blob-browser/jest.config.js delete mode 100644 packages/hash-blob-browser/karma.conf.js delete mode 100644 packages/hash-blob-browser/src/index.spec.ts delete mode 100644 packages/hash-node/jest.config.js delete mode 100644 packages/hash-node/src/index.spec.ts delete mode 100644 packages/hash-stream-node/jest.config.js delete mode 100644 packages/hash-stream-node/src/HashCalculator.spec.ts delete mode 100644 packages/hash-stream-node/src/HashCalculator.ts delete mode 100644 packages/hash-stream-node/src/fileStreamHasher.spec.ts delete mode 100644 packages/hash-stream-node/src/fileStreamHasher.ts delete mode 100644 packages/hash-stream-node/src/readableStreamHasher.spec.ts delete mode 100644 packages/hash-stream-node/src/readableStreamHasher.ts delete mode 100644 packages/invalid-dependency/jest.config.js delete mode 100644 packages/invalid-dependency/src/invalidFunction.spec.ts delete mode 100644 packages/invalid-dependency/src/invalidFunction.ts delete mode 100644 packages/invalid-dependency/src/invalidProvider.spec.ts delete mode 100644 packages/invalid-dependency/src/invalidProvider.ts delete mode 100644 packages/is-array-buffer/jest.config.js delete mode 100644 packages/is-array-buffer/src/index.spec.ts delete mode 100644 packages/md5-js/jest.config.js delete mode 100644 packages/md5-js/src/constants.ts delete mode 100644 packages/md5-js/src/index.spec.ts delete mode 100644 packages/middleware-apply-body-checksum/jest.config.js delete mode 100644 packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.spec.ts delete mode 100644 packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.ts delete mode 100644 packages/middleware-apply-body-checksum/src/index.spec.ts delete mode 100644 packages/middleware-apply-body-checksum/src/md5Configuration.ts delete mode 100644 packages/middleware-content-length/jest.config.js delete mode 100644 packages/middleware-endpoint/jest.config.integ.js delete mode 100644 packages/middleware-endpoint/jest.config.js delete mode 100644 packages/middleware-endpoint/src/adaptors/createConfigValueProvider.spec.ts delete mode 100644 packages/middleware-endpoint/src/adaptors/createConfigValueProvider.ts delete mode 100644 packages/middleware-endpoint/src/adaptors/getEndpointFromInstructions.ts delete mode 100644 packages/middleware-endpoint/src/adaptors/index.ts delete mode 100644 packages/middleware-endpoint/src/adaptors/toEndpointV1.ts delete mode 100644 packages/middleware-endpoint/src/endpointMiddleware.ts delete mode 100644 packages/middleware-endpoint/src/getEndpointPlugin.ts delete mode 100644 packages/middleware-endpoint/src/resolveEndpointConfig.ts delete mode 100644 packages/middleware-endpoint/src/service-customizations/index.ts delete mode 100644 packages/middleware-endpoint/src/service-customizations/s3.ts delete mode 100644 packages/middleware-endpoint/src/types.ts delete mode 100644 packages/middleware-retry/jest.config.js delete mode 100644 packages/middleware-retry/src/AdaptiveRetryStrategy.spec.ts delete mode 100644 packages/middleware-retry/src/AdaptiveRetryStrategy.ts delete mode 100644 packages/middleware-retry/src/StandardRetryStrategy.spec.ts delete mode 100644 packages/middleware-retry/src/StandardRetryStrategy.ts delete mode 100644 packages/middleware-retry/src/configurations.spec.ts delete mode 100644 packages/middleware-retry/src/configurations.ts delete mode 100644 packages/middleware-retry/src/defaultRetryQuota.spec.ts delete mode 100644 packages/middleware-retry/src/defaultRetryQuota.ts delete mode 100644 packages/middleware-retry/src/delayDecider.spec.ts delete mode 100644 packages/middleware-retry/src/delayDecider.ts delete mode 100644 packages/middleware-retry/src/omitRetryHeadersMiddleware.spec.ts delete mode 100644 packages/middleware-retry/src/omitRetryHeadersMiddleware.ts delete mode 100644 packages/middleware-retry/src/retryDecider.spec.ts delete mode 100644 packages/middleware-retry/src/retryDecider.ts delete mode 100644 packages/middleware-retry/src/retryMiddleware.spec.ts delete mode 100644 packages/middleware-retry/src/retryMiddleware.ts delete mode 100644 packages/middleware-retry/src/types.ts delete mode 100644 packages/middleware-retry/src/util.ts delete mode 100644 packages/middleware-serde/jest.config.js delete mode 100644 packages/middleware-serde/src/deserializerMiddleware.spec.ts delete mode 100644 packages/middleware-serde/src/deserializerMiddleware.ts delete mode 100644 packages/middleware-serde/src/serdePlugin.ts delete mode 100644 packages/middleware-serde/src/serializerMiddleware.spec.ts delete mode 100644 packages/middleware-serde/src/serializerMiddleware.ts delete mode 100644 packages/middleware-stack/jest.config.integ.js delete mode 100644 packages/middleware-stack/jest.config.js delete mode 100644 packages/middleware-stack/src/MiddlewareStack.spec.ts delete mode 100644 packages/middleware-stack/src/MiddlewareStack.ts delete mode 100644 packages/middleware-stack/src/types.ts delete mode 100644 packages/node-config-provider/jest.config.js delete mode 100644 packages/node-config-provider/src/configLoader.spec.ts delete mode 100644 packages/node-config-provider/src/configLoader.ts delete mode 100644 packages/node-config-provider/src/fromEnv.spec.ts delete mode 100644 packages/node-config-provider/src/fromEnv.ts delete mode 100644 packages/node-config-provider/src/fromSharedConfigFiles.spec.ts delete mode 100644 packages/node-config-provider/src/fromSharedConfigFiles.ts delete mode 100644 packages/node-config-provider/src/fromStatic.spec.ts delete mode 100644 packages/node-config-provider/src/fromStatic.ts delete mode 100644 packages/node-http-handler/fixtures/test-server-cert.pem delete mode 100644 packages/node-http-handler/fixtures/test-server-key.pem delete mode 100644 packages/node-http-handler/jest.config.js delete mode 100644 packages/node-http-handler/src/constants.ts delete mode 100644 packages/node-http-handler/src/get-transformed-headers.ts delete mode 100644 packages/node-http-handler/src/index.spec.ts delete mode 100644 packages/node-http-handler/src/node-http-handler.spec.ts delete mode 100644 packages/node-http-handler/src/node-http-handler.ts delete mode 100644 packages/node-http-handler/src/node-http2-connection-manager.ts delete mode 100644 packages/node-http-handler/src/node-http2-connection-pool.ts delete mode 100644 packages/node-http-handler/src/node-http2-handler.spec.ts delete mode 100644 packages/node-http-handler/src/node-http2-handler.ts delete mode 100644 packages/node-http-handler/src/readable.mock.ts delete mode 100644 packages/node-http-handler/src/server.mock.ts delete mode 100644 packages/node-http-handler/src/set-connection-timeout.spec.ts delete mode 100644 packages/node-http-handler/src/set-connection-timeout.ts delete mode 100644 packages/node-http-handler/src/set-socket-keep-alive.spec.ts delete mode 100644 packages/node-http-handler/src/set-socket-keep-alive.ts delete mode 100644 packages/node-http-handler/src/set-socket-timeout.spec.ts delete mode 100644 packages/node-http-handler/src/set-socket-timeout.ts delete mode 100644 packages/node-http-handler/src/stream-collector/collector.spec.ts delete mode 100644 packages/node-http-handler/src/stream-collector/collector.ts delete mode 100644 packages/node-http-handler/src/stream-collector/index.spec.ts delete mode 100644 packages/node-http-handler/src/stream-collector/index.ts delete mode 100644 packages/node-http-handler/src/stream-collector/readable.mock.ts delete mode 100644 packages/node-http-handler/src/write-request-body.spec.ts delete mode 100644 packages/node-http-handler/src/write-request-body.ts delete mode 100644 packages/property-provider/jest.config.js delete mode 100644 packages/property-provider/src/CredentialsProviderError.spec.ts delete mode 100644 packages/property-provider/src/CredentialsProviderError.ts delete mode 100644 packages/property-provider/src/ProviderError.spec.ts delete mode 100644 packages/property-provider/src/ProviderError.ts delete mode 100644 packages/property-provider/src/TokenProviderError.spec.ts delete mode 100644 packages/property-provider/src/TokenProviderError.ts delete mode 100644 packages/property-provider/src/chain.spec.ts delete mode 100755 packages/property-provider/src/chain.ts delete mode 100644 packages/property-provider/src/fromStatic.spec.ts delete mode 100644 packages/property-provider/src/fromStatic.ts delete mode 100644 packages/property-provider/src/memoize.spec.ts delete mode 100755 packages/property-provider/src/memoize.ts delete mode 100644 packages/protocol-http/jest.config.js delete mode 100644 packages/protocol-http/src/Field.ts delete mode 100644 packages/protocol-http/src/FieldPosition.ts delete mode 100644 packages/protocol-http/src/Fields.ts delete mode 100644 packages/protocol-http/src/httpHandler.ts delete mode 100644 packages/protocol-http/src/httpRequest.ts delete mode 100644 packages/protocol-http/src/httpResponse.ts delete mode 100644 packages/protocol-http/src/isValidHostname.spec.ts delete mode 100644 packages/protocol-http/src/isValidHostname.ts delete mode 100644 packages/querystring-builder/jest.config.js delete mode 100644 packages/querystring-parser/jest.config.js delete mode 100644 packages/querystring-parser/src/index.spec.ts delete mode 100644 packages/service-client-documentation-generator/src/sdk-client-toc-plugin.ts delete mode 100644 packages/service-client-documentation-generator/src/utils.ts delete mode 100644 packages/service-error-classification/jest.config.js delete mode 100644 packages/service-error-classification/src/constants.ts delete mode 100644 packages/service-error-classification/src/index.spec.ts delete mode 100644 packages/shared-ini-file-loader/jest.config.js delete mode 100644 packages/shared-ini-file-loader/src/getConfigFilepath.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/getConfigFilepath.ts delete mode 100644 packages/shared-ini-file-loader/src/getCredentialsFilepath.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/getCredentialsFilepath.ts delete mode 100644 packages/shared-ini-file-loader/src/getHomeDir.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/getHomeDir.ts delete mode 100644 packages/shared-ini-file-loader/src/getProfileData.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/getProfileData.ts delete mode 100644 packages/shared-ini-file-loader/src/getProfileName.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/getProfileName.ts delete mode 100644 packages/shared-ini-file-loader/src/getSSOTokenFilepath.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/getSSOTokenFilepath.ts delete mode 100644 packages/shared-ini-file-loader/src/getSSOTokenFromFile.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/getSSOTokenFromFile.ts delete mode 100644 packages/shared-ini-file-loader/src/getSsoSessionData.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/getSsoSessionData.ts delete mode 100644 packages/shared-ini-file-loader/src/loadSharedConfigFiles.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/loadSharedConfigFiles.ts delete mode 100644 packages/shared-ini-file-loader/src/loadSsoSessionData.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/loadSsoSessionData.ts delete mode 100644 packages/shared-ini-file-loader/src/mergeConfigFiles.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/mergeConfigFiles.ts delete mode 100644 packages/shared-ini-file-loader/src/parseIni.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/parseIni.ts delete mode 100644 packages/shared-ini-file-loader/src/parseKnownFiles.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/parseKnownFiles.ts delete mode 100644 packages/shared-ini-file-loader/src/slurpFile.spec.ts delete mode 100644 packages/shared-ini-file-loader/src/slurpFile.ts delete mode 100644 packages/shared-ini-file-loader/src/types.ts delete mode 100644 packages/signature-v4/jest.config.js delete mode 100644 packages/signature-v4/src/SignatureV4.spec.ts delete mode 100644 packages/signature-v4/src/SignatureV4.ts delete mode 100644 packages/signature-v4/src/cloneRequest.spec.ts delete mode 100644 packages/signature-v4/src/cloneRequest.ts delete mode 100644 packages/signature-v4/src/constants.ts delete mode 100644 packages/signature-v4/src/credentialDerivation.spec.ts delete mode 100644 packages/signature-v4/src/credentialDerivation.ts delete mode 100644 packages/signature-v4/src/getCanonicalHeaders.spec.ts delete mode 100644 packages/signature-v4/src/getCanonicalHeaders.ts delete mode 100644 packages/signature-v4/src/getCanonicalQuery.spec.ts delete mode 100644 packages/signature-v4/src/getCanonicalQuery.ts delete mode 100644 packages/signature-v4/src/getPayloadHash.spec.ts delete mode 100644 packages/signature-v4/src/getPayloadHash.ts delete mode 100644 packages/signature-v4/src/headerUtil.ts delete mode 100644 packages/signature-v4/src/moveHeadersToQuery.spec.ts delete mode 100644 packages/signature-v4/src/moveHeadersToQuery.ts delete mode 100644 packages/signature-v4/src/prepareRequest.spec.ts delete mode 100644 packages/signature-v4/src/prepareRequest.ts delete mode 100644 packages/signature-v4/src/suite.fixture.ts delete mode 100644 packages/signature-v4/src/suite.spec.ts delete mode 100644 packages/signature-v4/src/utilDate.spec.ts delete mode 100644 packages/signature-v4/src/utilDate.ts delete mode 100644 packages/smithy-client/.eslintrc.json delete mode 100644 packages/smithy-client/jest.config.js delete mode 100644 packages/smithy-client/src/NoOpLogger.ts delete mode 100644 packages/smithy-client/src/client.spec.ts delete mode 100644 packages/smithy-client/src/client.ts delete mode 100644 packages/smithy-client/src/collect-stream-body.spec.ts delete mode 100644 packages/smithy-client/src/collect-stream-body.ts delete mode 100644 packages/smithy-client/src/command.ts delete mode 100644 packages/smithy-client/src/constants.ts delete mode 100644 packages/smithy-client/src/create-aggregated-client.spec.ts delete mode 100644 packages/smithy-client/src/create-aggregated-client.ts delete mode 100644 packages/smithy-client/src/date-utils.spec.ts delete mode 100644 packages/smithy-client/src/date-utils.ts delete mode 100644 packages/smithy-client/src/default-error-handler.ts delete mode 100644 packages/smithy-client/src/defaults-mode.ts delete mode 100644 packages/smithy-client/src/emitWarningIfUnsupportedVersion.spec.ts delete mode 100644 packages/smithy-client/src/emitWarningIfUnsupportedVersion.ts delete mode 100644 packages/smithy-client/src/exceptions.spec.ts delete mode 100644 packages/smithy-client/src/exceptions.ts delete mode 100644 packages/smithy-client/src/extended-encode-uri-component.spec.ts delete mode 100644 packages/smithy-client/src/extended-encode-uri-component.ts delete mode 100644 packages/smithy-client/src/get-array-if-single-item.ts delete mode 100644 packages/smithy-client/src/get-value-from-text-node.spec.ts delete mode 100644 packages/smithy-client/src/get-value-from-text-node.ts delete mode 100644 packages/smithy-client/src/lazy-json.spec.ts delete mode 100644 packages/smithy-client/src/lazy-json.ts delete mode 100644 packages/smithy-client/src/object-mapping.spec.ts delete mode 100644 packages/smithy-client/src/object-mapping.ts delete mode 100644 packages/smithy-client/src/parse-utils.spec.ts delete mode 100644 packages/smithy-client/src/parse-utils.ts delete mode 100644 packages/smithy-client/src/resolve-path.ts delete mode 100644 packages/smithy-client/src/ser-utils.spec.ts delete mode 100644 packages/smithy-client/src/ser-utils.ts delete mode 100644 packages/smithy-client/src/serde-json.spec.ts delete mode 100644 packages/smithy-client/src/serde-json.ts delete mode 100644 packages/smithy-client/src/split-every.spec.ts delete mode 100644 packages/smithy-client/src/split-every.ts delete mode 100644 packages/url-parser/jest.config.js delete mode 100644 packages/url-parser/src/index.spec.ts delete mode 100644 packages/util-base64/jest.config.js delete mode 100644 packages/util-base64/src/__mocks__/testCases.json delete mode 100644 packages/util-base64/src/constants.browser.ts delete mode 100644 packages/util-base64/src/fromBase64.browser.spec.ts delete mode 100644 packages/util-base64/src/fromBase64.browser.ts delete mode 100644 packages/util-base64/src/fromBase64.spec.ts delete mode 100644 packages/util-base64/src/fromBase64.ts delete mode 100644 packages/util-base64/src/toBase64.browser.spec.ts delete mode 100644 packages/util-base64/src/toBase64.browser.ts delete mode 100644 packages/util-base64/src/toBase64.spec.ts delete mode 100644 packages/util-base64/src/toBase64.ts delete mode 100644 packages/util-body-length-browser/jest.config.js delete mode 100644 packages/util-body-length-browser/src/calculateBodyLength.spec.ts delete mode 100644 packages/util-body-length-browser/src/calculateBodyLength.ts delete mode 100644 packages/util-body-length-node/jest.config.js delete mode 100644 packages/util-body-length-node/src/calculateBodyLength.spec.ts delete mode 100644 packages/util-body-length-node/src/calculateBodyLength.ts delete mode 100644 packages/util-buffer-from/jest.config.js delete mode 100644 packages/util-buffer-from/src/index.spec.ts delete mode 100644 packages/util-config-provider/jest.config.js delete mode 100644 packages/util-config-provider/src/booleanSelector.spec.ts delete mode 100644 packages/util-config-provider/src/booleanSelector.ts delete mode 100644 packages/util-defaults-mode-browser/jest.config.js delete mode 100644 packages/util-defaults-mode-browser/src/constants.ts delete mode 100644 packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.native.spec.ts delete mode 100644 packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.native.ts delete mode 100644 packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.spec.ts delete mode 100644 packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.ts delete mode 100644 packages/util-defaults-mode-node/jest.config.js delete mode 100644 packages/util-defaults-mode-node/src/constants.ts delete mode 100644 packages/util-defaults-mode-node/src/defaultsModeConfig.ts delete mode 100644 packages/util-defaults-mode-node/src/resolveDefaultsModeConfig.spec.ts delete mode 100644 packages/util-defaults-mode-node/src/resolveDefaultsModeConfig.ts delete mode 100644 packages/util-hex-encoding/jest.config.js delete mode 100644 packages/util-hex-encoding/src/index.spec.ts delete mode 100644 packages/util-middleware/jest.config.js delete mode 100644 packages/util-middleware/src/normalizeProvider.spec.ts delete mode 100644 packages/util-middleware/src/normalizeProvider.ts delete mode 100644 packages/util-retry/jest.config.js delete mode 100644 packages/util-retry/src/AdaptiveRetryStrategy.spec.ts delete mode 100644 packages/util-retry/src/AdaptiveRetryStrategy.ts delete mode 100644 packages/util-retry/src/ConfiguredRetryStrategy.spec.ts delete mode 100644 packages/util-retry/src/ConfiguredRetryStrategy.ts delete mode 100644 packages/util-retry/src/DefaultRateLimiter.spec.ts delete mode 100644 packages/util-retry/src/DefaultRateLimiter.ts delete mode 100644 packages/util-retry/src/StandardRetryStrategy.spec.ts delete mode 100644 packages/util-retry/src/StandardRetryStrategy.ts delete mode 100644 packages/util-retry/src/config.ts delete mode 100644 packages/util-retry/src/constants.ts delete mode 100644 packages/util-retry/src/defaultRetryBackoffStrategy.spec.ts delete mode 100644 packages/util-retry/src/defaultRetryBackoffStrategy.ts delete mode 100644 packages/util-retry/src/defaultRetryToken.spec.ts delete mode 100644 packages/util-retry/src/defaultRetryToken.ts delete mode 100644 packages/util-retry/src/types.ts delete mode 100644 packages/util-stream-browser/jest.config.js delete mode 100644 packages/util-stream-browser/src/getAwsChunkedEncodingStream.ts delete mode 100644 packages/util-stream-browser/src/sdk-stream-mixin.spec.ts delete mode 100644 packages/util-stream-browser/src/sdk-stream-mixin.ts delete mode 100644 packages/util-stream-node/jest.config.js delete mode 100644 packages/util-stream-node/src/getAwsChunkedEncodingStream.spec.ts delete mode 100644 packages/util-stream-node/src/getAwsChunkedEncodingStream.ts delete mode 100644 packages/util-stream-node/src/sdk-stream-mixin.spec.ts delete mode 100644 packages/util-stream-node/src/sdk-stream-mixin.ts delete mode 100644 packages/util-stream/jest.config.integ.js delete mode 100644 packages/util-stream/jest.config.js delete mode 100644 packages/util-stream/karma.conf.js delete mode 100644 packages/util-stream/src/blob/Uint8ArrayBlobAdapter.spec.ts delete mode 100644 packages/util-stream/src/blob/Uint8ArrayBlobAdapter.ts delete mode 100644 packages/util-stream/src/blob/transforms.ts delete mode 100644 packages/util-stream/src/getAwsChunkedEncodingStream.browser.spec.ts delete mode 100644 packages/util-stream/src/getAwsChunkedEncodingStream.browser.ts delete mode 100644 packages/util-stream/src/getAwsChunkedEncodingStream.spec.ts delete mode 100644 packages/util-stream/src/getAwsChunkedEncodingStream.ts delete mode 100644 packages/util-stream/src/sdk-stream-mixin.browser.spec.ts delete mode 100644 packages/util-stream/src/sdk-stream-mixin.browser.ts delete mode 100644 packages/util-stream/src/sdk-stream-mixin.spec.ts delete mode 100644 packages/util-stream/src/sdk-stream-mixin.ts delete mode 100644 packages/util-uri-escape/jest.config.js delete mode 100644 packages/util-uri-escape/src/escape-uri-path.spec.ts delete mode 100644 packages/util-uri-escape/src/escape-uri-path.ts delete mode 100644 packages/util-uri-escape/src/escape-uri.spec.ts delete mode 100644 packages/util-uri-escape/src/escape-uri.ts delete mode 100644 packages/util-utf8/jest.config.js delete mode 100644 packages/util-utf8/src/fromUtf8.browser.spec.ts delete mode 100644 packages/util-utf8/src/fromUtf8.browser.ts delete mode 100644 packages/util-utf8/src/fromUtf8.spec.ts delete mode 100644 packages/util-utf8/src/fromUtf8.ts delete mode 100644 packages/util-utf8/src/toUint8Array.spec.ts delete mode 100644 packages/util-utf8/src/toUint8Array.ts delete mode 100644 packages/util-utf8/src/toUtf8.browser.spec.ts delete mode 100644 packages/util-utf8/src/toUtf8.browser.ts delete mode 100644 packages/util-utf8/src/toUtf8.spec.ts delete mode 100644 packages/util-utf8/src/toUtf8.ts delete mode 100644 packages/util-waiter/.eslintrc.json delete mode 100644 packages/util-waiter/jest.config.js delete mode 100644 packages/util-waiter/src/createWaiter.spec.ts delete mode 100644 packages/util-waiter/src/createWaiter.ts delete mode 100644 packages/util-waiter/src/index.spec.ts delete mode 100644 packages/util-waiter/src/poller.spec.ts delete mode 100644 packages/util-waiter/src/poller.ts delete mode 100644 packages/util-waiter/src/utils/index.ts delete mode 100644 packages/util-waiter/src/utils/sleep.spec.ts delete mode 100644 packages/util-waiter/src/utils/sleep.ts delete mode 100644 packages/util-waiter/src/utils/validate.spec.ts delete mode 100644 packages/util-waiter/src/utils/validate.ts delete mode 100644 packages/util-waiter/src/waiter.ts rename packages/util-stream/src/util-stream.integ.spec.ts => private/aws-middleware-test/src/util-stream.spec.ts (92%) diff --git a/packages/abort-controller/jest.config.js b/packages/abort-controller/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/abort-controller/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/abort-controller/package.json b/packages/abort-controller/package.json index 3791a0010309..517d0fbe9539 100644 --- a/packages/abort-controller/package.json +++ b/packages/abort-controller/package.json @@ -14,7 +14,7 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "extract:docs": "api-extractor run --local", - "test": "jest" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", @@ -22,7 +22,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "*", + "@smithy/abort-controller": "^1.0.1", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/abort-controller/src/AbortController.spec.ts b/packages/abort-controller/src/AbortController.spec.ts deleted file mode 100644 index 41ea646ae2d3..000000000000 --- a/packages/abort-controller/src/AbortController.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AbortController } from "./AbortController"; -import { AbortSignal } from "./AbortSignal"; - -jest.useFakeTimers(); - -describe("AbortController", () => { - it("should communicate cancellation via its signal", () => { - const source = new AbortController(); - const { signal } = source; - expect(signal).toBeInstanceOf(AbortSignal); - expect(signal.aborted).toBe(false); - - source.abort(); - expect(signal.aborted).toBe(true); - }); -}); diff --git a/packages/abort-controller/src/AbortController.ts b/packages/abort-controller/src/AbortController.ts deleted file mode 100644 index 23c70fb4785c..000000000000 --- a/packages/abort-controller/src/AbortController.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { AbortController as IAbortController } from "@aws-sdk/types"; - -import { AbortSignal } from "./AbortSignal"; - -export { IAbortController }; - -/** - * This implementation was added as Node.js didn't support AbortController prior to 15.x - * Use native implementation in browsers or Node.js \>=15.4.0. - * - * @public - */ -export class AbortController implements IAbortController { - public readonly signal: AbortSignal = new AbortSignal(); - - abort(): void { - this.signal.abort(); - } -} diff --git a/packages/abort-controller/src/AbortSignal.spec.ts b/packages/abort-controller/src/AbortSignal.spec.ts deleted file mode 100644 index cbfb7610014f..000000000000 --- a/packages/abort-controller/src/AbortSignal.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { AbortController } from "./AbortController"; - -describe("AbortSignal", () => { - it("should report aborted to be false until the signal is aborted", () => { - const controller = new AbortController(); - const { signal } = controller; - expect(signal.aborted).toBe(false); - - controller.abort(); - expect(signal.aborted).toBe(true); - }); - - it("should invoke the onabort handler when the signal is aborted", () => { - const controller = new AbortController(); - const { signal } = controller; - const abortHandler = jest.fn(); - signal.onabort = abortHandler; - expect(abortHandler.mock.calls.length).toBe(0); - controller.abort(); - expect(abortHandler.mock.calls.length).toBe(1); - }); - - it("should not invoke the onabort handler multiple time", () => { - const controller = new AbortController(); - const { signal } = controller; - const abortHandler = jest.fn(); - signal.onabort = abortHandler; - expect(abortHandler.mock.calls.length).toBe(0); - controller.abort(); - expect(abortHandler.mock.calls.length).toBe(1); - controller.abort(); - expect(abortHandler.mock.calls.length).toBe(1); - }); -}); diff --git a/packages/abort-controller/src/AbortSignal.ts b/packages/abort-controller/src/AbortSignal.ts deleted file mode 100644 index 278438e93588..000000000000 --- a/packages/abort-controller/src/AbortSignal.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { AbortHandler, AbortSignal as IAbortSignal } from "@aws-sdk/types"; - -export { AbortHandler, IAbortSignal }; - -/** - * @public - */ -export class AbortSignal implements IAbortSignal { - public onabort: AbortHandler | null = null; - private _aborted = false; - - constructor() { - Object.defineProperty(this, "_aborted", { - value: false, - writable: true, - }); - } - - /** - * Whether the associated operation has already been cancelled. - */ - get aborted(): boolean { - return this._aborted; - } - - /** - * @internal - */ - abort(): void { - this._aborted = true; - if (this.onabort) { - this.onabort(this); - this.onabort = null; - } - } -} diff --git a/packages/abort-controller/src/index.ts b/packages/abort-controller/src/index.ts index 8788e2f1af78..e3a75d9c402a 100644 --- a/packages/abort-controller/src/index.ts +++ b/packages/abort-controller/src/index.ts @@ -1,9 +1 @@ -/** - * This implementation was added as Node.js didn't support AbortController prior to 15.x - * Use native implementation in browsers or Node.js \>=15.4.0. - * - * @deprecated Use standard implementations in [Browsers](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) and [Node.js](https://nodejs.org/docs/latest/api/globals.html#class-abortcontroller) - * @packageDocumentation - */ -export * from "./AbortController"; -export * from "./AbortSignal"; +export * from "@smithy/abort-controller"; diff --git a/packages/chunked-blob-reader-native/jest.config.js b/packages/chunked-blob-reader-native/jest.config.js deleted file mode 100644 index bd895a5df03e..000000000000 --- a/packages/chunked-blob-reader-native/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, - testEnvironment: "jsdom", -}; diff --git a/packages/chunked-blob-reader-native/package.json b/packages/chunked-blob-reader-native/package.json index 2f51de01874f..fd28e752954c 100644 --- a/packages/chunked-blob-reader-native/package.json +++ b/packages/chunked-blob-reader-native/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,7 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/util-base64": "*", + "@smithy/chunked-blob-reader-native": "^1.0.1", "tslib": "^2.5.0" }, "typesVersions": { diff --git a/packages/chunked-blob-reader-native/src/index.spec.ts b/packages/chunked-blob-reader-native/src/index.spec.ts deleted file mode 100644 index 290696968bbd..000000000000 --- a/packages/chunked-blob-reader-native/src/index.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { blobReader } from "./index"; - -describe("blobReader", () => { - it("reads an entire blob", async () => { - const longMessage: number[] = []; - for (let i = 0; i < 1024 * 1024 * 5; i += 4) { - longMessage.push(102, 111, 111, 32); // 'foo ' - } - const blob = new Blob([Uint8Array.from(longMessage)]); - - let totalBytes = 0; - - await blobReader(blob, (chunk) => { - totalBytes += chunk.byteLength; - }); - - expect(totalBytes).toBe(1024 * 1024 * 5); - }); - - it("respects the chunk size", async () => { - const message = new Uint8Array(100); - message.fill(0); - - const blob = new Blob([message]); - - const chunkSizes: number[] = []; - - await blobReader( - blob, - (chunk) => { - chunkSizes.push(chunk.byteLength); - }, - 12 // chunk size in bytes - ); - - expect(chunkSizes.length).toBe(9); - - for (let i = 0; i < chunkSizes.length; i++) { - if (i < chunkSizes.length - 1) { - expect(chunkSizes[i]).toBe(12); - } else { - expect(chunkSizes[i]).toBe(4); - } - } - }); -}); diff --git a/packages/chunked-blob-reader-native/src/index.ts b/packages/chunked-blob-reader-native/src/index.ts index 055f96050f4c..905b8386dccc 100644 --- a/packages/chunked-blob-reader-native/src/index.ts +++ b/packages/chunked-blob-reader-native/src/index.ts @@ -1,43 +1 @@ -import { fromBase64 } from "@aws-sdk/util-base64"; -/** - * @internal - */ -export function blobReader( - blob: Blob, - onChunk: (chunk: Uint8Array) => void, - chunkSize: number = 1024 * 1024 -): Promise { - return new Promise((resolve, reject) => { - const fileReader = new FileReader(); - - fileReader.onerror = reject; - fileReader.onabort = reject; - - const size = blob.size; - let totalBytesRead = 0; - - const read = () => { - if (totalBytesRead >= size) { - resolve(); - return; - } - fileReader.readAsDataURL(blob.slice(totalBytesRead, Math.min(size, totalBytesRead + chunkSize))); - }; - - fileReader.onload = (event) => { - const result = (event.target as any).result; - // reference: https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL - // response from readAsDataURL is always prepended with "data:*/*;base64," - const dataOffset = result.indexOf(",") + 1; - const data = result.substring(dataOffset); - const decoded = fromBase64(data); - onChunk(decoded); - totalBytesRead += decoded.byteLength; - // read the next block - read(); - }; - - // kick off the read - read(); - }); -} +export * from "@smithy/chunked-blob-reader-native"; diff --git a/packages/chunked-blob-reader/jest.config.js b/packages/chunked-blob-reader/jest.config.js deleted file mode 100644 index bd895a5df03e..000000000000 --- a/packages/chunked-blob-reader/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, - testEnvironment: "jsdom", -}; diff --git a/packages/chunked-blob-reader/package.json b/packages/chunked-blob-reader/package.json index 8a9027f5456b..fab063522164 100644 --- a/packages/chunked-blob-reader/package.json +++ b/packages/chunked-blob-reader/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,6 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { + "@smithy/chunked-blob-reader": "^1.0.1", "tslib": "^2.5.0" }, "typesVersions": { diff --git a/packages/chunked-blob-reader/src/index.spec.ts b/packages/chunked-blob-reader/src/index.spec.ts deleted file mode 100644 index 290696968bbd..000000000000 --- a/packages/chunked-blob-reader/src/index.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { blobReader } from "./index"; - -describe("blobReader", () => { - it("reads an entire blob", async () => { - const longMessage: number[] = []; - for (let i = 0; i < 1024 * 1024 * 5; i += 4) { - longMessage.push(102, 111, 111, 32); // 'foo ' - } - const blob = new Blob([Uint8Array.from(longMessage)]); - - let totalBytes = 0; - - await blobReader(blob, (chunk) => { - totalBytes += chunk.byteLength; - }); - - expect(totalBytes).toBe(1024 * 1024 * 5); - }); - - it("respects the chunk size", async () => { - const message = new Uint8Array(100); - message.fill(0); - - const blob = new Blob([message]); - - const chunkSizes: number[] = []; - - await blobReader( - blob, - (chunk) => { - chunkSizes.push(chunk.byteLength); - }, - 12 // chunk size in bytes - ); - - expect(chunkSizes.length).toBe(9); - - for (let i = 0; i < chunkSizes.length; i++) { - if (i < chunkSizes.length - 1) { - expect(chunkSizes[i]).toBe(12); - } else { - expect(chunkSizes[i]).toBe(4); - } - } - }); -}); diff --git a/packages/chunked-blob-reader/src/index.ts b/packages/chunked-blob-reader/src/index.ts index b3fffeaec1c5..220c646bd0e1 100644 --- a/packages/chunked-blob-reader/src/index.ts +++ b/packages/chunked-blob-reader/src/index.ts @@ -1,37 +1 @@ -/** - * @internal - */ -export function blobReader( - blob: Blob, - onChunk: (chunk: Uint8Array) => void, - chunkSize: number = 1024 * 1024 -): Promise { - return new Promise((resolve, reject) => { - const fileReader = new FileReader(); - - fileReader.addEventListener("error", reject); - fileReader.addEventListener("abort", reject); - - const size = blob.size; - let totalBytesRead = 0; - - function read() { - if (totalBytesRead >= size) { - resolve(); - return; - } - fileReader.readAsArrayBuffer(blob.slice(totalBytesRead, Math.min(size, totalBytesRead + chunkSize))); - } - - fileReader.addEventListener("load", (event) => { - const result = (event.target as any).result; - onChunk(new Uint8Array(result)); - totalBytesRead += result.byteLength; - // read the next block - read(); - }); - - // kick off the read - read(); - }); -} +export * from "@smithy/chunked-blob-reader"; diff --git a/packages/config-resolver/jest.config.js b/packages/config-resolver/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/config-resolver/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/config-resolver/package.json b/packages/config-resolver/package.json index 035c85b8e89d..020ff27135ae 100644 --- a/packages/config-resolver/package.json +++ b/packages/config-resolver/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,9 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "*", - "@aws-sdk/util-config-provider": "*", - "@aws-sdk/util-middleware": "*", + "@smithy/config-resolver": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/config-resolver/src/endpointsConfig/NodeUseDualstackEndpointConfigOptions.spec.ts b/packages/config-resolver/src/endpointsConfig/NodeUseDualstackEndpointConfigOptions.spec.ts deleted file mode 100644 index 90d0aa8efc73..000000000000 --- a/packages/config-resolver/src/endpointsConfig/NodeUseDualstackEndpointConfigOptions.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { booleanSelector, SelectorType } from "@aws-sdk/util-config-provider"; - -import { - CONFIG_USE_DUALSTACK_ENDPOINT, - DEFAULT_USE_DUALSTACK_ENDPOINT, - ENV_USE_DUALSTACK_ENDPOINT, - NODE_USE_DUALSTACK_ENDPOINT_CONFIG_OPTIONS, -} from "./NodeUseDualstackEndpointConfigOptions"; - -jest.mock("@aws-sdk/util-config-provider"); - -describe("NODE_USE_DUALSTACK_ENDPOINT_CONFIG_OPTIONS", () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - const test = (func: Function, obj: Record, key: string, type: SelectorType) => { - it.each([true, false, undefined])("returns %s", (output) => { - (booleanSelector as jest.Mock).mockReturnValueOnce(output); - expect(func(obj)).toEqual(output); - expect(booleanSelector).toBeCalledWith(obj, key, type); - }); - - it("throws error", () => { - const mockError = new Error("error"); - (booleanSelector as jest.Mock).mockImplementationOnce(() => { - throw mockError; - }); - expect(() => { - func(obj); - }).toThrow(mockError); - }); - }; - - describe("calls booleanSelector for environmentVariableSelector", () => { - const env: { [ENV_USE_DUALSTACK_ENDPOINT]: any } = {} as any; - const { environmentVariableSelector } = NODE_USE_DUALSTACK_ENDPOINT_CONFIG_OPTIONS; - test(environmentVariableSelector, env, ENV_USE_DUALSTACK_ENDPOINT, SelectorType.ENV); - }); - - describe("calls booleanSelector for configFileSelector", () => { - const profileContent: { [CONFIG_USE_DUALSTACK_ENDPOINT]: any } = {} as any; - const { configFileSelector } = NODE_USE_DUALSTACK_ENDPOINT_CONFIG_OPTIONS; - test(configFileSelector, profileContent, CONFIG_USE_DUALSTACK_ENDPOINT, SelectorType.CONFIG); - }); - - it("returns false for default", () => { - const { default: defaultValue } = NODE_USE_DUALSTACK_ENDPOINT_CONFIG_OPTIONS; - expect(defaultValue).toEqual(DEFAULT_USE_DUALSTACK_ENDPOINT); - }); -}); diff --git a/packages/config-resolver/src/endpointsConfig/NodeUseDualstackEndpointConfigOptions.ts b/packages/config-resolver/src/endpointsConfig/NodeUseDualstackEndpointConfigOptions.ts deleted file mode 100644 index 94707fef095b..000000000000 --- a/packages/config-resolver/src/endpointsConfig/NodeUseDualstackEndpointConfigOptions.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { LoadedConfigSelectors } from "@aws-sdk/node-config-provider"; -import { booleanSelector, SelectorType } from "@aws-sdk/util-config-provider"; - -/** - * @internal - */ -export const ENV_USE_DUALSTACK_ENDPOINT = "AWS_USE_DUALSTACK_ENDPOINT"; -/** - * @internal - */ -export const CONFIG_USE_DUALSTACK_ENDPOINT = "use_dualstack_endpoint"; -/** - * @internal - */ -export const DEFAULT_USE_DUALSTACK_ENDPOINT = false; - -/** - * @internal - */ -export const NODE_USE_DUALSTACK_ENDPOINT_CONFIG_OPTIONS: LoadedConfigSelectors = { - environmentVariableSelector: (env: NodeJS.ProcessEnv) => - booleanSelector(env, ENV_USE_DUALSTACK_ENDPOINT, SelectorType.ENV), - configFileSelector: (profile) => booleanSelector(profile, CONFIG_USE_DUALSTACK_ENDPOINT, SelectorType.CONFIG), - default: false, -}; diff --git a/packages/config-resolver/src/endpointsConfig/NodeUseFipsEndpointConfigOptions.spec.ts b/packages/config-resolver/src/endpointsConfig/NodeUseFipsEndpointConfigOptions.spec.ts deleted file mode 100644 index a9ff8d7a3095..000000000000 --- a/packages/config-resolver/src/endpointsConfig/NodeUseFipsEndpointConfigOptions.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { booleanSelector, SelectorType } from "@aws-sdk/util-config-provider"; - -import { - CONFIG_USE_FIPS_ENDPOINT, - DEFAULT_USE_FIPS_ENDPOINT, - ENV_USE_FIPS_ENDPOINT, - NODE_USE_FIPS_ENDPOINT_CONFIG_OPTIONS, -} from "./NodeUseFipsEndpointConfigOptions"; - -jest.mock("@aws-sdk/util-config-provider"); - -describe("NODE_USE_FIPS_ENDPOINT_CONFIG_OPTIONS", () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - const test = (func: Function, obj: Record, key: string, type: SelectorType) => { - it.each([true, false, undefined])("returns %s", (output) => { - (booleanSelector as jest.Mock).mockReturnValueOnce(output); - expect(func(obj)).toEqual(output); - expect(booleanSelector).toBeCalledWith(obj, key, type); - }); - - it("throws error", () => { - const mockError = new Error("error"); - (booleanSelector as jest.Mock).mockImplementationOnce(() => { - throw mockError; - }); - expect(() => { - func(obj); - }).toThrow(mockError); - }); - }; - - describe("calls booleanSelector for environmentVariableSelector", () => { - const env: { [ENV_USE_FIPS_ENDPOINT]: any } = {} as any; - const { environmentVariableSelector } = NODE_USE_FIPS_ENDPOINT_CONFIG_OPTIONS; - test(environmentVariableSelector, env, ENV_USE_FIPS_ENDPOINT, SelectorType.ENV); - }); - - describe("calls booleanSelector for configFileSelector", () => { - const profileContent: { [CONFIG_USE_FIPS_ENDPOINT]: any } = {} as any; - const { configFileSelector } = NODE_USE_FIPS_ENDPOINT_CONFIG_OPTIONS; - test(configFileSelector, profileContent, CONFIG_USE_FIPS_ENDPOINT, SelectorType.CONFIG); - }); - - it("returns false for default", () => { - const { default: defaultValue } = NODE_USE_FIPS_ENDPOINT_CONFIG_OPTIONS; - expect(defaultValue).toEqual(DEFAULT_USE_FIPS_ENDPOINT); - }); -}); diff --git a/packages/config-resolver/src/endpointsConfig/NodeUseFipsEndpointConfigOptions.ts b/packages/config-resolver/src/endpointsConfig/NodeUseFipsEndpointConfigOptions.ts deleted file mode 100644 index 88de4eeb8883..000000000000 --- a/packages/config-resolver/src/endpointsConfig/NodeUseFipsEndpointConfigOptions.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { LoadedConfigSelectors } from "@aws-sdk/node-config-provider"; -import { booleanSelector, SelectorType } from "@aws-sdk/util-config-provider"; - -/** - * @internal - */ -export const ENV_USE_FIPS_ENDPOINT = "AWS_USE_FIPS_ENDPOINT"; -/** - * @internal - */ -export const CONFIG_USE_FIPS_ENDPOINT = "use_fips_endpoint"; -/** - * @internal - */ -export const DEFAULT_USE_FIPS_ENDPOINT = false; - -/** - * @internal - */ -export const NODE_USE_FIPS_ENDPOINT_CONFIG_OPTIONS: LoadedConfigSelectors = { - environmentVariableSelector: (env: NodeJS.ProcessEnv) => - booleanSelector(env, ENV_USE_FIPS_ENDPOINT, SelectorType.ENV), - configFileSelector: (profile) => booleanSelector(profile, CONFIG_USE_FIPS_ENDPOINT, SelectorType.CONFIG), - default: false, -}; diff --git a/packages/config-resolver/src/endpointsConfig/index.ts b/packages/config-resolver/src/endpointsConfig/index.ts deleted file mode 100644 index ea1cf59a0757..000000000000 --- a/packages/config-resolver/src/endpointsConfig/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @internal - */ -export * from "./NodeUseDualstackEndpointConfigOptions"; -/** - * @internal - */ -export * from "./NodeUseFipsEndpointConfigOptions"; -/** - * @internal - */ -export * from "./resolveCustomEndpointsConfig"; -/** - * @internal - */ -export * from "./resolveEndpointsConfig"; diff --git a/packages/config-resolver/src/endpointsConfig/resolveCustomEndpointsConfig.spec.ts b/packages/config-resolver/src/endpointsConfig/resolveCustomEndpointsConfig.spec.ts deleted file mode 100644 index e608457db090..000000000000 --- a/packages/config-resolver/src/endpointsConfig/resolveCustomEndpointsConfig.spec.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { normalizeProvider } from "@aws-sdk/util-middleware"; - -import { resolveCustomEndpointsConfig } from "./resolveCustomEndpointsConfig"; - -jest.mock("@aws-sdk/util-middleware"); - -describe(resolveCustomEndpointsConfig.name, () => { - const mockEndpoint = { - protocol: "http:", - hostname: "localhost", - path: "/", - }; - - const mockInput = { - endpoint: mockEndpoint, - urlParser: jest.fn(() => mockEndpoint), - useDualstackEndpoint: () => Promise.resolve(false), - } as any; - - beforeEach(() => { - (normalizeProvider as jest.Mock).mockImplementation((input) => - typeof input === "function" ? input : () => Promise.resolve(input) - ); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("tls", () => { - afterEach(() => { - expect(normalizeProvider).toHaveBeenCalledTimes(2); - expect(normalizeProvider).toHaveBeenNthCalledWith(2, mockInput.useDualstackEndpoint); - }); - - it.each([true, false])("returns %s when the value is passed", (tls) => { - expect(resolveCustomEndpointsConfig({ ...mockInput, tls }).tls).toStrictEqual(tls); - }); - - it("returns true if input.tls is undefined", () => { - expect(resolveCustomEndpointsConfig({ ...mockInput }).tls).toStrictEqual(true); - }); - }); - - it("returns true for isCustomEndpoint", () => { - expect(resolveCustomEndpointsConfig(mockInput).isCustomEndpoint).toStrictEqual(true); - }); - - it("returns false when useDualstackEndpoint is not defined", async () => { - const useDualstackEndpoint = await resolveCustomEndpointsConfig({ - ...mockInput, - useDualstackEndpoint: undefined, - }).useDualstackEndpoint(); - expect(useDualstackEndpoint).toStrictEqual(false); - }); - - describe("returns normalized endpoint", () => { - afterEach(() => { - expect(normalizeProvider).toHaveBeenCalledTimes(2); - expect(normalizeProvider).toHaveBeenNthCalledWith(1, mockInput.endpoint); - expect(normalizeProvider).toHaveBeenNthCalledWith(2, mockInput.useDualstackEndpoint); - }); - - it("calls urlParser endpoint is of type string", async () => { - const mockEndpointString = "http://localhost/"; - const endpoint = await resolveCustomEndpointsConfig({ ...mockInput, endpoint: mockEndpointString }).endpoint(); - expect(endpoint).toStrictEqual(mockEndpoint); - expect(mockInput.urlParser).toHaveBeenCalledWith(mockEndpointString); - }); - - it("passes endpoint to normalize if not string", async () => { - const endpoint = await resolveCustomEndpointsConfig(mockInput).endpoint(); - expect(endpoint).toStrictEqual(mockEndpoint); - expect(mockInput.urlParser).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/packages/config-resolver/src/endpointsConfig/resolveCustomEndpointsConfig.ts b/packages/config-resolver/src/endpointsConfig/resolveCustomEndpointsConfig.ts deleted file mode 100644 index 8baa2360cf18..000000000000 --- a/packages/config-resolver/src/endpointsConfig/resolveCustomEndpointsConfig.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Endpoint, Provider, UrlParser } from "@aws-sdk/types"; -import { normalizeProvider } from "@aws-sdk/util-middleware"; - -import { EndpointsInputConfig, EndpointsResolvedConfig } from "./resolveEndpointsConfig"; - -/** - * @internal - */ -export interface CustomEndpointsInputConfig extends EndpointsInputConfig { - /** - * The fully qualified endpoint of the webservice. - */ - endpoint: string | Endpoint | Provider; -} - -interface PreviouslyResolved { - urlParser: UrlParser; -} - -/** - * @internal - */ -export interface CustomEndpointsResolvedConfig extends EndpointsResolvedConfig { - /** - * Whether the endpoint is specified by caller. - * @internal - */ - isCustomEndpoint: true; -} - -/** - * @internal - */ -export const resolveCustomEndpointsConfig = ( - input: T & CustomEndpointsInputConfig & PreviouslyResolved -): T & CustomEndpointsResolvedConfig => { - const { endpoint, urlParser } = input; - return { - ...input, - tls: input.tls ?? true, - endpoint: normalizeProvider(typeof endpoint === "string" ? urlParser(endpoint) : endpoint), - isCustomEndpoint: true, - useDualstackEndpoint: normalizeProvider(input.useDualstackEndpoint ?? false), - }; -}; diff --git a/packages/config-resolver/src/endpointsConfig/resolveEndpointsConfig.spec.ts b/packages/config-resolver/src/endpointsConfig/resolveEndpointsConfig.spec.ts deleted file mode 100644 index 0248fcef64af..000000000000 --- a/packages/config-resolver/src/endpointsConfig/resolveEndpointsConfig.spec.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { normalizeProvider } from "@aws-sdk/util-middleware"; - -import { resolveEndpointsConfig } from "./resolveEndpointsConfig"; -import { getEndpointFromRegion } from "./utils/getEndpointFromRegion"; - -jest.mock("@aws-sdk/util-middleware"); -jest.mock("./utils/getEndpointFromRegion"); - -describe(resolveEndpointsConfig.name, () => { - const mockEndpoint = { - protocol: "http:", - hostname: "localhost", - path: "/", - }; - - const mockInput = { - endpoint: mockEndpoint, - urlParser: jest.fn(() => mockEndpoint), - useDualstackEndpoint: () => Promise.resolve(false), - useFipsEndpoint: () => Promise.resolve(false), - } as any; - - beforeEach(() => { - (getEndpointFromRegion as jest.Mock).mockResolvedValueOnce(mockEndpoint); - (normalizeProvider as jest.Mock).mockImplementation((input) => - typeof input === "function" ? input : () => Promise.resolve(input) - ); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("tls", () => { - afterEach(() => { - expect(normalizeProvider).toHaveBeenNthCalledWith(1, mockInput.useDualstackEndpoint); - }); - - it.each([true, false])("returns %s when it's %s", (tls) => { - expect(resolveEndpointsConfig({ ...mockInput, tls }).tls).toStrictEqual(tls); - }); - - it("returns true is input.tls is undefined", () => { - expect(resolveEndpointsConfig({ ...mockInput }).tls).toStrictEqual(true); - }); - }); - - describe("isCustomEndpoint", () => { - afterEach(() => { - expect(normalizeProvider).toHaveBeenNthCalledWith(1, mockInput.useDualstackEndpoint); - }); - - it("returns true when endpoint is defined", () => { - expect(resolveEndpointsConfig(mockInput).isCustomEndpoint).toStrictEqual(true); - }); - - it("returns false when endpoint is not defined", () => { - const { endpoint, ...mockInputWithoutEndpoint } = mockInput; - expect(resolveEndpointsConfig(mockInputWithoutEndpoint).isCustomEndpoint).toStrictEqual(false); - }); - }); - - it("returns false when useDualstackEndpoint is not defined", async () => { - const useDualstackEndpoint = await resolveEndpointsConfig({ - ...mockInput, - useDualstackEndpoint: undefined, - }).useDualstackEndpoint(); - expect(useDualstackEndpoint).toStrictEqual(false); - }); - - describe("endpoint", () => { - afterEach(() => { - expect(normalizeProvider).toHaveBeenNthCalledWith(1, mockInput.useDualstackEndpoint); - }); - - describe("returns from normalizeProvider when endpoint is defined", () => { - afterEach(() => { - expect(normalizeProvider).toHaveBeenCalledTimes(2); - expect(normalizeProvider).toHaveBeenNthCalledWith(2, mockInput.endpoint); - expect(getEndpointFromRegion).not.toHaveBeenCalled(); - }); - - it("calls urlParser endpoint is of type string", async () => { - const mockEndpointString = "http://localhost/"; - const endpoint = await resolveEndpointsConfig({ ...mockInput, endpoint: mockEndpointString }).endpoint(); - expect(endpoint).toStrictEqual(mockEndpoint); - expect(mockInput.urlParser).toHaveBeenCalledWith(mockEndpointString); - }); - - it("passes endpoint to normalize if not string", async () => { - const endpoint = await resolveEndpointsConfig(mockInput).endpoint(); - expect(endpoint).toStrictEqual(mockEndpoint); - expect(mockInput.urlParser).not.toHaveBeenCalled(); - }); - }); - - it("returns from getEndpointFromRegion when endpoint is not defined", async () => { - const { endpoint, ...mockInputWithoutEndpoint } = mockInput; - const returnedEndpoint = await resolveEndpointsConfig(mockInputWithoutEndpoint).endpoint(); - expect(returnedEndpoint).toStrictEqual(mockEndpoint); - expect(normalizeProvider).toHaveBeenCalledTimes(1); - expect(getEndpointFromRegion).toHaveBeenCalledTimes(1); - expect(getEndpointFromRegion).toHaveBeenCalledWith(mockInputWithoutEndpoint); - }); - }); -}); diff --git a/packages/config-resolver/src/endpointsConfig/resolveEndpointsConfig.ts b/packages/config-resolver/src/endpointsConfig/resolveEndpointsConfig.ts deleted file mode 100644 index f07c1cb5604d..000000000000 --- a/packages/config-resolver/src/endpointsConfig/resolveEndpointsConfig.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Endpoint, Provider, RegionInfoProvider, UrlParser } from "@aws-sdk/types"; -import { normalizeProvider } from "@aws-sdk/util-middleware"; - -import { getEndpointFromRegion } from "./utils/getEndpointFromRegion"; - -/** - * @internal - */ -export interface EndpointsInputConfig { - /** - * The fully qualified endpoint of the webservice. This is only required when using - * a custom endpoint (for example, when using a local version of S3). - */ - endpoint?: string | Endpoint | Provider; - - /** - * Whether TLS is enabled for requests. - */ - tls?: boolean; - - /** - * Enables IPv6/IPv4 dualstack endpoint. - */ - useDualstackEndpoint?: boolean | Provider; -} - -interface PreviouslyResolved { - regionInfoProvider: RegionInfoProvider; - urlParser: UrlParser; - region: Provider; - useFipsEndpoint: Provider; -} - -/** - * @internal - */ -export interface EndpointsResolvedConfig extends Required { - /** - * Resolved value for input {@link EndpointsInputConfig.endpoint} - */ - endpoint: Provider; - - /** - * Whether the endpoint is specified by caller. - * @internal - */ - isCustomEndpoint?: boolean; - - /** - * Resolved value for input {@link EndpointsInputConfig.useDualstackEndpoint} - */ - useDualstackEndpoint: Provider; -} - -/** - * @internal - * - * @deprecated endpoints rulesets use @aws-sdk/middleware-endpoint resolveEndpointConfig. - * All generated clients should migrate to Endpoints 2.0 endpointRuleSet traits. - */ -export const resolveEndpointsConfig = ( - input: T & EndpointsInputConfig & PreviouslyResolved -): T & EndpointsResolvedConfig => { - const useDualstackEndpoint = normalizeProvider(input.useDualstackEndpoint ?? false); - const { endpoint, useFipsEndpoint, urlParser } = input; - return { - ...input, - tls: input.tls ?? true, - endpoint: endpoint - ? normalizeProvider(typeof endpoint === "string" ? urlParser(endpoint) : endpoint) - : () => getEndpointFromRegion({ ...input, useDualstackEndpoint, useFipsEndpoint }), - isCustomEndpoint: !!endpoint, - useDualstackEndpoint, - }; -}; diff --git a/packages/config-resolver/src/endpointsConfig/utils/getEndpointFromRegion.spec.ts b/packages/config-resolver/src/endpointsConfig/utils/getEndpointFromRegion.spec.ts deleted file mode 100644 index 7e40182a21a8..000000000000 --- a/packages/config-resolver/src/endpointsConfig/utils/getEndpointFromRegion.spec.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { getEndpointFromRegion } from "./getEndpointFromRegion"; - -describe(getEndpointFromRegion.name, () => { - const mockRegion = jest.fn(); - const mockUrlParser = jest.fn(); - const mockRegionInfoProvider = jest.fn(); - const mockUseFipsEndpoint = jest.fn(); - const mockUseDualstackEndpoint = jest.fn(); - - const mockInput = { - region: mockRegion, - urlParser: mockUrlParser, - regionInfoProvider: mockRegionInfoProvider, - useDualstackEndpoint: mockUseDualstackEndpoint, - useFipsEndpoint: mockUseFipsEndpoint, - }; - - const mockRegionValue = "mockRegion"; - const mockEndpoint = { - protocol: "http:", - hostname: "localhost", - path: "/", - }; - const mockRegionInfo = { hostname: "mockHostname" }; - - beforeEach(() => { - mockRegion.mockResolvedValue(mockRegionValue); - mockUrlParser.mockResolvedValue(mockEndpoint); - mockRegionInfoProvider.mockResolvedValue(mockRegionInfo); - mockUseFipsEndpoint.mockResolvedValue(false); - mockUseDualstackEndpoint.mockResolvedValue(false); - }); - - afterEach(() => { - expect(mockRegion).toHaveBeenCalledTimes(1); - jest.clearAllMocks(); - }); - - describe("tls", () => { - afterEach(() => { - expect(mockRegionInfoProvider).toHaveBeenCalledWith(mockRegionValue, { - useDualstackEndpoint: false, - useFipsEndpoint: false, - }); - }); - - it("uses protocol https when not defined", async () => { - await getEndpointFromRegion(mockInput); - expect(mockUrlParser).toHaveBeenCalledTimes(1); - expect(mockUrlParser).toHaveBeenCalledWith(`https://${mockRegionInfo.hostname}`); - }); - - it.each([ - ["http:", false], - ["https:", true], - ])("uses protocol %s when set to %s", async (protocol, tls) => { - await getEndpointFromRegion({ ...mockInput, tls }); - expect(mockUrlParser).toHaveBeenCalledTimes(1); - expect(mockUrlParser).toHaveBeenCalledWith(`${protocol}//${mockRegionInfo.hostname}`); - }); - }); - - describe("throws if region is invalid", () => { - const errorMsg = "Invalid region in client config"; - it.each([ - "", - "has_underscore", - "-starts-with-dash", - "ends-with-dash-", - "-starts-and-ends-with-dash-", - "-", - "a-", - "c0nt@in$-$ymb01$", - "a".repeat(64), - ])("region: %s", async (region) => { - mockRegion.mockResolvedValue(region); - try { - await getEndpointFromRegion(mockInput); - fail(`expected Error: ${errorMsg}`); - } catch (error) { - expect(error.message).toEqual(errorMsg); - } - expect(mockRegionInfoProvider).not.toHaveBeenCalled(); - expect(mockUrlParser).not.toHaveBeenCalled(); - }); - }); - - it("throws if hostname is not returned by regionInfoProvider", async () => { - mockRegionInfoProvider.mockResolvedValue({}); - const errorMsg = "Cannot resolve hostname from client config"; - try { - await getEndpointFromRegion(mockInput); - fail(`expected Error: ${errorMsg}`); - } catch (error) { - expect(error.message).toEqual(errorMsg); - } - expect(mockRegionInfoProvider).toHaveBeenCalledWith(mockRegionValue, { - useDualstackEndpoint: false, - useFipsEndpoint: false, - }); - expect(mockUrlParser).not.toHaveBeenCalled(); - }); - - it("returns parsed endpoint", async () => { - const endpoint = await getEndpointFromRegion(mockInput); - expect(endpoint).toEqual(mockEndpoint); - expect(mockRegionInfoProvider).toHaveBeenCalledWith(mockRegionValue, { - useDualstackEndpoint: false, - useFipsEndpoint: false, - }); - expect(mockUrlParser).toHaveBeenCalledWith(`https://${mockRegionInfo.hostname}`); - }); -}); diff --git a/packages/config-resolver/src/endpointsConfig/utils/getEndpointFromRegion.ts b/packages/config-resolver/src/endpointsConfig/utils/getEndpointFromRegion.ts deleted file mode 100644 index 8483cf6e30d4..000000000000 --- a/packages/config-resolver/src/endpointsConfig/utils/getEndpointFromRegion.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Provider, RegionInfoProvider, UrlParser } from "@aws-sdk/types"; - -interface GetEndpointFromRegionOptions { - region: Provider; - tls?: boolean; - regionInfoProvider: RegionInfoProvider; - urlParser: UrlParser; - useDualstackEndpoint: Provider; - useFipsEndpoint: Provider; -} - -export const getEndpointFromRegion = async (input: GetEndpointFromRegionOptions) => { - const { tls = true } = input; - const region = await input.region(); - - const dnsHostRegex = new RegExp(/^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/); - if (!dnsHostRegex.test(region)) { - throw new Error("Invalid region in client config"); - } - - const useDualstackEndpoint = await input.useDualstackEndpoint(); - const useFipsEndpoint = await input.useFipsEndpoint(); - const { hostname } = (await input.regionInfoProvider(region, { useDualstackEndpoint, useFipsEndpoint })) ?? {}; - if (!hostname) { - throw new Error("Cannot resolve hostname from client config"); - } - - return input.urlParser(`${tls ? "https:" : "http:"}//${hostname}`); -}; diff --git a/packages/config-resolver/src/index.ts b/packages/config-resolver/src/index.ts index fde708604997..add50d5d1a35 100644 --- a/packages/config-resolver/src/index.ts +++ b/packages/config-resolver/src/index.ts @@ -1,12 +1 @@ -/** - * @internal - */ -export * from "./endpointsConfig"; -/** - * @internal - */ -export * from "./regionConfig"; -/** - * @internal - */ -export * from "./regionInfo"; +export * from "@smithy/config-resolver"; diff --git a/packages/config-resolver/src/regionConfig/config.spec.ts b/packages/config-resolver/src/regionConfig/config.spec.ts deleted file mode 100644 index 580f3e85280d..000000000000 --- a/packages/config-resolver/src/regionConfig/config.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - NODE_REGION_CONFIG_FILE_OPTIONS, - NODE_REGION_CONFIG_OPTIONS, - REGION_ENV_NAME, - REGION_INI_NAME, -} from "./config"; - -describe("config", () => { - describe("NODE_REGION_CONFIG_OPTIONS", () => { - describe("environmentVariableSelector", () => { - const { environmentVariableSelector } = NODE_REGION_CONFIG_OPTIONS; - it.each([undefined, "mockRegion"])(`when env[${REGION_ENV_NAME}]: %s`, (mockEndpoint) => { - expect(environmentVariableSelector({ [REGION_ENV_NAME]: mockEndpoint })).toBe(mockEndpoint); - }); - }); - - describe("configFileSelector", () => { - const { configFileSelector } = NODE_REGION_CONFIG_OPTIONS; - it.each([undefined, "mockRegion"])(`when env[${REGION_INI_NAME}]: %s`, (mockEndpoint) => { - expect(configFileSelector({ [REGION_INI_NAME]: mockEndpoint })).toBe(mockEndpoint); - }); - }); - - it("default throws error", () => { - const { default: defaultKey } = NODE_REGION_CONFIG_OPTIONS; - expect(() => { - (defaultKey as any)(); - }).toThrowError(new Error("Region is missing")); - }); - }); - - describe("NODE_REGION_CONFIG_FILE_OPTIONS", () => { - it("preferredFile contains credentials", () => { - const { preferredFile } = NODE_REGION_CONFIG_FILE_OPTIONS; - expect(preferredFile).toBe("credentials"); - }); - }); -}); diff --git a/packages/config-resolver/src/regionConfig/config.ts b/packages/config-resolver/src/regionConfig/config.ts deleted file mode 100644 index 970f17657edc..000000000000 --- a/packages/config-resolver/src/regionConfig/config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { LoadedConfigSelectors, LocalConfigOptions } from "@aws-sdk/node-config-provider"; - -/** - * @internal - */ -export const REGION_ENV_NAME = "AWS_REGION"; -/** - * @internal - */ -export const REGION_INI_NAME = "region"; - -/** - * @internal - */ -export const NODE_REGION_CONFIG_OPTIONS: LoadedConfigSelectors = { - environmentVariableSelector: (env) => env[REGION_ENV_NAME], - configFileSelector: (profile) => profile[REGION_INI_NAME], - default: () => { - throw new Error("Region is missing"); - }, -}; - -/** - * @internal - */ -export const NODE_REGION_CONFIG_FILE_OPTIONS: LocalConfigOptions = { - preferredFile: "credentials", -}; diff --git a/packages/config-resolver/src/regionConfig/getRealRegion.spec.ts b/packages/config-resolver/src/regionConfig/getRealRegion.spec.ts deleted file mode 100644 index 5a27f4aa8303..000000000000 --- a/packages/config-resolver/src/regionConfig/getRealRegion.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { getRealRegion } from "./getRealRegion"; -import { isFipsRegion } from "./isFipsRegion"; - -jest.mock("./isFipsRegion"); - -describe(getRealRegion.name, () => { - beforeEach(() => { - (isFipsRegion as jest.Mock).mockReturnValue(true); - }); - - afterEach(() => { - expect(isFipsRegion).toHaveBeenCalledTimes(1); - jest.clearAllMocks(); - }); - - it("returns provided region if it's not FIPS", () => { - const mockRegion = "mockRegion"; - (isFipsRegion as jest.Mock).mockReturnValue(false); - expect(getRealRegion(mockRegion)).toStrictEqual(mockRegion); - }); - - describe("FIPS regions", () => { - it.each(["fips-aws-global", "aws-fips"])(`returns "us-east-1" for "%s"`, (input) => { - expect(getRealRegion(input)).toStrictEqual("us-east-1"); - }); - - it.each([ - ["us-west-1", "us-west-1-fips"], - ["us-west-1", "fips-us-west-1"], - ["us-west-1", "fips-dkr-us-west-1"], - ["us-west-1", "fips-prod-us-west-1"], - ])(`returns "%s" for "%s"`, (output, input) => { - expect(getRealRegion(input)).toStrictEqual(output); - }); - }); -}); diff --git a/packages/config-resolver/src/regionConfig/getRealRegion.ts b/packages/config-resolver/src/regionConfig/getRealRegion.ts deleted file mode 100644 index 5a9eb9ce10a9..000000000000 --- a/packages/config-resolver/src/regionConfig/getRealRegion.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { isFipsRegion } from "./isFipsRegion"; - -/** - * @internal - */ -export const getRealRegion = (region: string) => - isFipsRegion(region) - ? ["fips-aws-global", "aws-fips"].includes(region) - ? "us-east-1" - : region.replace(/fips-(dkr-|prod-)?|-fips/, "") - : region; diff --git a/packages/config-resolver/src/regionConfig/index.ts b/packages/config-resolver/src/regionConfig/index.ts deleted file mode 100644 index 6dcf5e55f976..000000000000 --- a/packages/config-resolver/src/regionConfig/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @internal - */ -export * from "./config"; -/** - * @internal - */ -export * from "./resolveRegionConfig"; diff --git a/packages/config-resolver/src/regionConfig/isFipsRegion.spec.ts b/packages/config-resolver/src/regionConfig/isFipsRegion.spec.ts deleted file mode 100644 index a497e75f2660..000000000000 --- a/packages/config-resolver/src/regionConfig/isFipsRegion.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { isFipsRegion } from "./isFipsRegion"; - -describe(isFipsRegion.name, () => { - it.each([ - [true, "fips-us-east-1"], - [true, "us-east-1-fips"], - [false, "us-east-1"], - ])(`returns %s for region "%s"`, (output, input) => { - expect(isFipsRegion(input)).toEqual(output); - }); - - it.each([undefined, null])("returns false for %s", (input) => { - expect(isFipsRegion(input)).toEqual(false); - }); -}); diff --git a/packages/config-resolver/src/regionConfig/isFipsRegion.ts b/packages/config-resolver/src/regionConfig/isFipsRegion.ts deleted file mode 100644 index 5a7d60f7f05d..000000000000 --- a/packages/config-resolver/src/regionConfig/isFipsRegion.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * @internal - */ -export const isFipsRegion = (region: string) => - typeof region === "string" && (region.startsWith("fips-") || region.endsWith("-fips")); diff --git a/packages/config-resolver/src/regionConfig/resolveRegionConfig.spec.ts b/packages/config-resolver/src/regionConfig/resolveRegionConfig.spec.ts deleted file mode 100644 index cd01e2077790..000000000000 --- a/packages/config-resolver/src/regionConfig/resolveRegionConfig.spec.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { getRealRegion } from "./getRealRegion"; -import { isFipsRegion } from "./isFipsRegion"; -import { resolveRegionConfig } from "./resolveRegionConfig"; - -jest.mock("./getRealRegion"); -jest.mock("./isFipsRegion"); - -describe("RegionConfig", () => { - const mockRegion = "mockRegion"; - const mockRealRegion = "mockRealRegion"; - const mockUseFipsEndpoint = () => Promise.resolve(false); - - beforeEach(() => { - (getRealRegion as jest.Mock).mockReturnValue(mockRealRegion); - (isFipsRegion as jest.Mock).mockReturnValue(false); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("region", () => { - it("return normalized value with real region if passed as a string", async () => { - const resolvedRegionConfig = resolveRegionConfig({ region: mockRegion, useFipsEndpoint: mockUseFipsEndpoint }); - const resolvedRegion = await resolvedRegionConfig.region(); - expect(resolvedRegion).toBe(mockRealRegion); - expect(getRealRegion).toHaveBeenCalledTimes(1); - expect(getRealRegion).toHaveBeenCalledWith(mockRegion); - }); - - it("return provider with real region if passed as a Provider", async () => { - const resolvedRegionConfig = resolveRegionConfig({ - region: () => Promise.resolve(mockRegion), - useFipsEndpoint: mockUseFipsEndpoint, - }); - const resolvedRegion = await resolvedRegionConfig.region(); - expect(resolvedRegion).toBe(mockRealRegion); - expect(getRealRegion).toHaveBeenCalledTimes(1); - expect(getRealRegion).toHaveBeenCalledWith(mockRegion); - }); - - it("throw if region is not supplied", () => { - expect(() => resolveRegionConfig({ useFipsEndpoint: mockUseFipsEndpoint })).toThrow(); - }); - }); - - describe("useFipsEndpoint", () => { - let mockRegionProvider; - let mockUseFipsEndpoint; - - beforeEach(() => { - mockRegionProvider = jest.fn().mockResolvedValueOnce(Promise.resolve(mockRegion)); - mockUseFipsEndpoint = jest.fn().mockResolvedValueOnce(Promise.resolve(false)); - }); - - afterEach(() => { - expect(isFipsRegion).toHaveBeenCalledTimes(1); - expect(isFipsRegion).toHaveBeenCalledWith(mockRegion); - expect(mockRegionProvider).toHaveBeenCalledTimes(1); - }); - - it("can be undefined", async () => { - const resolvedRegionConfig = resolveRegionConfig({ - region: mockRegionProvider, - }); - - expect(await resolvedRegionConfig.useFipsEndpoint()).toBe(false); - }); - - it("returns Provider which returns true for FIPS endpoints", async () => { - (isFipsRegion as jest.Mock).mockReturnValue(true); - const resolvedRegionConfig = resolveRegionConfig({ - region: mockRegionProvider, - useFipsEndpoint: mockUseFipsEndpoint, - }); - - const useFipsEndpoint = await resolvedRegionConfig.useFipsEndpoint(); - expect(useFipsEndpoint).toStrictEqual(true); - expect(mockUseFipsEndpoint).not.toHaveBeenCalled(); - }); - - it("returns passed Provider if endpoint is not FIPS", async () => { - const resolvedRegionConfig = resolveRegionConfig({ - region: mockRegionProvider, - useFipsEndpoint: mockUseFipsEndpoint, - }); - - const useFipsEndpoint = await resolvedRegionConfig.useFipsEndpoint(); - expect(useFipsEndpoint).toStrictEqual(false); - expect(mockUseFipsEndpoint).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/packages/config-resolver/src/regionConfig/resolveRegionConfig.ts b/packages/config-resolver/src/regionConfig/resolveRegionConfig.ts deleted file mode 100644 index a9fa43ee7bfd..000000000000 --- a/packages/config-resolver/src/regionConfig/resolveRegionConfig.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Provider } from "@aws-sdk/types"; - -import { getRealRegion } from "./getRealRegion"; -import { isFipsRegion } from "./isFipsRegion"; - -/** - * @internal - */ -export interface RegionInputConfig { - /** - * The AWS region to which this client will send requests - */ - region?: string | Provider; - - /** - * Enables FIPS compatible endpoints. - */ - useFipsEndpoint?: boolean | Provider; -} - -interface PreviouslyResolved {} - -/** - * @internal - */ -export interface RegionResolvedConfig { - /** - * Resolved value for input config {@link RegionInputConfig.region} - */ - region: Provider; - - /** - * Resolved value for input {@link RegionInputConfig.useFipsEndpoint} - */ - useFipsEndpoint: Provider; -} - -/** - * @internal - */ -export const resolveRegionConfig = (input: T & RegionInputConfig & PreviouslyResolved): T & RegionResolvedConfig => { - const { region, useFipsEndpoint } = input; - - if (!region) { - throw new Error("Region is missing"); - } - - return { - ...input, - region: async () => { - if (typeof region === "string") { - return getRealRegion(region); - } - const providedRegion = await region(); - return getRealRegion(providedRegion); - }, - useFipsEndpoint: async () => { - const providedRegion = typeof region === "string" ? region : await region(); - if (isFipsRegion(providedRegion)) { - return true; - } - return typeof useFipsEndpoint !== "function" ? Promise.resolve(!!useFipsEndpoint) : useFipsEndpoint(); - }, - }; -}; diff --git a/packages/config-resolver/src/regionInfo/EndpointVariant.ts b/packages/config-resolver/src/regionInfo/EndpointVariant.ts deleted file mode 100644 index aa6218a95c82..000000000000 --- a/packages/config-resolver/src/regionInfo/EndpointVariant.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { EndpointVariantTag } from "./EndpointVariantTag"; - -/** - * @internal - * - * Provides hostname information for specific host label. - */ -export type EndpointVariant = { - hostname: string; - tags: EndpointVariantTag[]; -}; diff --git a/packages/config-resolver/src/regionInfo/EndpointVariantTag.ts b/packages/config-resolver/src/regionInfo/EndpointVariantTag.ts deleted file mode 100644 index 8f51a799a325..000000000000 --- a/packages/config-resolver/src/regionInfo/EndpointVariantTag.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @internal - * - * The tag which mentions which area variant is providing information for. - * Can be either "fips" or "dualstack". - */ -export type EndpointVariantTag = "fips" | "dualstack"; diff --git a/packages/config-resolver/src/regionInfo/PartitionHash.ts b/packages/config-resolver/src/regionInfo/PartitionHash.ts deleted file mode 100644 index 41399ac217ee..000000000000 --- a/packages/config-resolver/src/regionInfo/PartitionHash.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { EndpointVariant } from "./EndpointVariant"; - -/** - * @internal - * - * The hash of partition with the information specific to that partition. - * The information includes the list of regions belonging to that partition, - * and the hostname to be used for the partition. - */ -export type PartitionHash = Record< - string, - { - regions: string[]; - regionRegex: string; - variants: EndpointVariant[]; - endpoint?: string; - } ->; diff --git a/packages/config-resolver/src/regionInfo/RegionHash.ts b/packages/config-resolver/src/regionInfo/RegionHash.ts deleted file mode 100644 index 68691b3e6302..000000000000 --- a/packages/config-resolver/src/regionInfo/RegionHash.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { EndpointVariant } from "./EndpointVariant"; - -/** - * @internal - * - * The hash of region with the information specific to that region. - * The information can include hostname, signingService and signingRegion. - */ -export type RegionHash = Record< - string, - { - variants: EndpointVariant[]; - signingService?: string; - signingRegion?: string; - } ->; diff --git a/packages/config-resolver/src/regionInfo/getHostnameFromVariants.spec.ts b/packages/config-resolver/src/regionInfo/getHostnameFromVariants.spec.ts deleted file mode 100644 index ed2fa8dea83a..000000000000 --- a/packages/config-resolver/src/regionInfo/getHostnameFromVariants.spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { EndpointVariant } from "./EndpointVariant"; -import { getHostnameFromVariants, GetHostnameFromVariantsOptions } from "./getHostnameFromVariants"; - -describe(getHostnameFromVariants.name, () => { - const getMockHostname = (options: GetHostnameFromVariantsOptions) => JSON.stringify(options); - const getMockTags = ({ useFipsEndpoint, useDualstackEndpoint }: GetHostnameFromVariantsOptions) => [ - ...(useFipsEndpoint ? ["fips"] : []), - ...(useDualstackEndpoint ? ["dualstack"] : []), - ]; - const getMockVariants = () => - [ - { useFipsEndpoint: false, useDualstackEndpoint: false }, - { useFipsEndpoint: false, useDualstackEndpoint: true }, - { useFipsEndpoint: true, useDualstackEndpoint: false }, - { useFipsEndpoint: true, useDualstackEndpoint: true }, - ].map((options) => ({ - hostname: getMockHostname(options), - tags: getMockTags(options), - })); - - const testCases = [ - [false, false], - [false, true], - [true, false], - [true, true], - ]; - - describe("returns hostname if present in variants", () => { - it.each(testCases)("useFipsEndpoint: %s, useDualstackEndpoint: %s", (useFipsEndpoint, useDualstackEndpoint) => { - const options = { useFipsEndpoint, useDualstackEndpoint }; - const variants = getMockVariants() as EndpointVariant[]; - expect(getHostnameFromVariants(variants, options)).toEqual(getMockHostname(options)); - }); - }); - - describe("returns undefined if not present in variants", () => { - it.each(testCases)("useFipsEndpoint: %s, useDualstackEndpoint: %s", (useFipsEndpoint, useDualstackEndpoint) => { - const options = { useFipsEndpoint, useDualstackEndpoint }; - const variants = getMockVariants() as EndpointVariant[]; - expect( - getHostnameFromVariants( - variants.filter(({ tags }) => JSON.stringify(tags) !== JSON.stringify(getMockTags(options))), - options - ) - ).toBeUndefined(); - }); - }); - - describe("returns undefined if variants in undefined", () => { - it.each(testCases)("useFipsEndpoint: %s, useDualstackEndpoint: %s", (useFipsEndpoint, useDualstackEndpoint) => { - const options = { useFipsEndpoint, useDualstackEndpoint }; - expect(getHostnameFromVariants(undefined, options)).toBeUndefined(); - }); - }); -}); diff --git a/packages/config-resolver/src/regionInfo/getHostnameFromVariants.ts b/packages/config-resolver/src/regionInfo/getHostnameFromVariants.ts deleted file mode 100644 index 1200de5489ad..000000000000 --- a/packages/config-resolver/src/regionInfo/getHostnameFromVariants.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { EndpointVariant } from "./EndpointVariant"; - -/** - * @internal - */ -export interface GetHostnameFromVariantsOptions { - useFipsEndpoint: boolean; - useDualstackEndpoint: boolean; -} - -/** - * @internal - */ -export const getHostnameFromVariants = ( - variants: EndpointVariant[] = [], - { useFipsEndpoint, useDualstackEndpoint }: GetHostnameFromVariantsOptions -) => - variants.find( - ({ tags }) => useFipsEndpoint === tags.includes("fips") && useDualstackEndpoint === tags.includes("dualstack") - )?.hostname; diff --git a/packages/config-resolver/src/regionInfo/getRegionInfo.spec.ts b/packages/config-resolver/src/regionInfo/getRegionInfo.spec.ts deleted file mode 100644 index 963444746b66..000000000000 --- a/packages/config-resolver/src/regionInfo/getRegionInfo.spec.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { getHostnameFromVariants } from "./getHostnameFromVariants"; -import { getRegionInfo } from "./getRegionInfo"; -import { getResolvedHostname } from "./getResolvedHostname"; -import { getResolvedPartition } from "./getResolvedPartition"; -import { getResolvedSigningRegion } from "./getResolvedSigningRegion"; -import { PartitionHash } from "./PartitionHash"; -import { RegionHash } from "./RegionHash"; - -jest.mock("./getHostnameFromVariants"); -jest.mock("./getResolvedHostname"); -jest.mock("./getResolvedPartition"); -jest.mock("./getResolvedSigningRegion"); - -describe(getRegionInfo.name, () => { - const mockPartition = "mockPartition"; - const mockSigningService = "mockSigningService"; - - const mockRegion = "mockRegion"; - const mockRegionRegex = "mockRegionRegex"; - const mockHostname = "{region}.mockHostname.com"; - const mockEndpointRegion = "mockEndpointRegion"; - const mockEndpointHostname = "{region}.mockEndpointHostname.com"; - - enum RegionCase { - REGION = "Region", - ENDPOINT = "Endpoint", - REGION_AND_ENDPOINT = "Region and Endpoint", - } - - const getMockRegionHash = (regionCase: RegionCase): RegionHash => ({ - ...((regionCase === RegionCase.REGION || regionCase === RegionCase.REGION_AND_ENDPOINT) && { - [mockRegion]: { - variants: [{ hostname: mockHostname, tags: [] }], - }, - }), - ...((regionCase === RegionCase.ENDPOINT || regionCase === RegionCase.REGION_AND_ENDPOINT) && { - [mockEndpointRegion]: { - variants: [{ hostname: mockEndpointHostname, tags: [] }], - }, - }), - }); - - const getMockPartitionHash = (regionCase: RegionCase): PartitionHash => ({ - [mockPartition]: { - regions: [mockRegion, `${mockRegion}2`, `${mockRegion}3`], - regionRegex: mockRegionRegex, - variants: [{ hostname: mockHostname, tags: [] }], - ...((regionCase === RegionCase.ENDPOINT || regionCase === RegionCase.REGION_AND_ENDPOINT) && { - endpoint: mockEndpointRegion, - }), - }, - }); - - const getMockResolvedRegion = (regionCase: RegionCase): string => - regionCase !== RegionCase.ENDPOINT ? mockRegion : mockEndpointRegion; - - const getMockResolvedPartitionOptions = (partitionHash) => ({ partitionHash }); - - const getMockRegionInfoOptions = (regionHash, getResolvedPartitionOptions) => ({ - ...getResolvedPartitionOptions, - signingService: mockSigningService, - regionHash, - }); - - beforeEach(() => { - (getHostnameFromVariants as jest.Mock).mockReturnValue(mockHostname); - (getResolvedHostname as jest.Mock).mockReturnValue(mockHostname); - (getResolvedPartition as jest.Mock).mockReturnValue(mockPartition); - (getResolvedSigningRegion as jest.Mock).mockReturnValue(undefined); - }); - - afterEach(() => { - expect(getHostnameFromVariants).toHaveBeenCalledTimes(2); - expect(getResolvedHostname).toHaveBeenCalledTimes(1); - expect(getResolvedPartition).toHaveBeenCalledTimes(1); - jest.clearAllMocks(); - }); - - describe("returns data based on options passed", () => { - it.each(Object.values(RegionCase))("%s", (regionCase) => { - const mockRegionHash = getMockRegionHash(regionCase); - const mockPartitionHash = getMockPartitionHash(regionCase); - - const mockGetResolvedPartitionOptions = getMockResolvedPartitionOptions(mockPartitionHash); - const mockGetRegionInfoOptions = getMockRegionInfoOptions(mockRegionHash, mockGetResolvedPartitionOptions); - - const mockResolvedRegion = getMockResolvedRegion(regionCase); - const mockRegionHostname = mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname; - const mockPartitionHostname = mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname; - - (getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockRegionHostname); - (getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockPartitionHostname); - - expect(getRegionInfo(mockRegion, mockGetRegionInfoOptions)).toEqual({ - signingService: mockSigningService, - hostname: mockHostname, - partition: mockPartition, - }); - - expect(getResolvedHostname).toHaveBeenCalledWith(mockResolvedRegion, { - regionHostname: mockRegionHostname, - partitionHostname: mockPartitionHostname, - }); - expect(getResolvedPartition).toHaveBeenCalledWith(mockRegion, mockGetResolvedPartitionOptions); - expect(getResolvedSigningRegion).toHaveBeenCalledWith(mockHostname, { - regionRegex: mockRegionRegex, - useFipsEndpoint: false, - }); - }); - }); - - describe("returns signingRegion if resolved by getResolvedSigningRegion", () => { - const getMockRegionHashWithSigningRegion = ( - regionCase: RegionCase, - mockRegionHash: RegionHash, - mockSigningRegion: string - ): RegionHash => ({ - ...mockRegionHash, - ...((regionCase === RegionCase.REGION || regionCase === RegionCase.REGION_AND_ENDPOINT) && { - [mockRegion]: { - ...mockRegionHash[mockRegion], - signingRegion: mockSigningRegion, - }, - }), - ...((regionCase === RegionCase.ENDPOINT || regionCase === RegionCase.REGION_AND_ENDPOINT) && { - [mockEndpointRegion]: { - ...mockRegionHash[mockEndpointRegion], - signingRegion: mockSigningRegion, - }, - }), - }); - - it.each(Object.values(RegionCase))("%s", (regionCase) => { - const mockSigningRegion = "mockSigningRegion"; - (getResolvedSigningRegion as jest.Mock).mockReturnValueOnce(mockSigningRegion); - const mockRegionHash = getMockRegionHash(regionCase); - const mockPartitionHash = getMockPartitionHash(regionCase); - - const mockGetResolvedPartitionOptions = getMockResolvedPartitionOptions(mockPartitionHash); - const mockGetRegionInfoOptions = getMockRegionInfoOptions(mockRegionHash, mockGetResolvedPartitionOptions); - - const mockResolvedRegion = getMockResolvedRegion(regionCase); - const mockRegionHostname = mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname; - const mockPartitionHostname = mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname; - - (getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockRegionHostname); - (getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockPartitionHostname); - - const mockRegionHashWithSigningRegion = getMockRegionHashWithSigningRegion( - regionCase, - mockRegionHash, - mockSigningRegion - ); - - expect( - getRegionInfo(mockRegion, { ...mockGetRegionInfoOptions, regionHash: mockRegionHashWithSigningRegion }) - ).toEqual({ - signingService: mockSigningService, - hostname: mockHostname, - partition: mockPartition, - signingRegion: mockSigningRegion, - }); - - expect(getResolvedHostname).toHaveBeenCalledWith(mockResolvedRegion, { - regionHostname: mockRegionHostname, - partitionHostname: mockPartitionHostname, - }); - expect(getResolvedPartition).toHaveBeenCalledWith(mockRegion, mockGetResolvedPartitionOptions); - expect(getResolvedSigningRegion).toHaveBeenCalledWith(mockHostname, { - signingRegion: mockSigningRegion, - regionRegex: mockRegionRegex, - useFipsEndpoint: false, - }); - }); - }); - - describe("returns signingService if present in regionHash", () => { - const getMockRegionHashWithSigningService = ( - regionCase: RegionCase, - mockRegionHash: RegionHash, - mockSigningService: string - ): RegionHash => ({ - ...mockRegionHash, - ...((regionCase === RegionCase.REGION || regionCase === RegionCase.REGION_AND_ENDPOINT) && { - [mockRegion]: { - ...mockRegionHash[mockRegion], - signingService: mockSigningService, - }, - }), - ...((regionCase === RegionCase.ENDPOINT || regionCase === RegionCase.REGION_AND_ENDPOINT) && { - [mockEndpointRegion]: { - ...mockRegionHash[mockEndpointRegion], - signingService: mockSigningService, - }, - }), - }); - - it.each(Object.values(RegionCase))("%s", (regionCase) => { - const mockSigningServiceInRegionHash = "mockSigningServiceInRegionHash"; - const mockRegionHash = getMockRegionHash(regionCase); - const mockPartitionHash = getMockPartitionHash(regionCase); - - const mockGetResolvedPartitionOptions = getMockResolvedPartitionOptions(mockPartitionHash); - const mockGetRegionInfoOptions = getMockRegionInfoOptions(mockRegionHash, mockGetResolvedPartitionOptions); - - const mockResolvedRegion = getMockResolvedRegion(regionCase); - const mockRegionHostname = mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname; - const mockPartitionHostname = mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname; - - (getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockRegionHostname); - (getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockPartitionHostname); - - const mockRegionHashWithSigningRegion = getMockRegionHashWithSigningService( - regionCase, - mockRegionHash, - mockSigningServiceInRegionHash - ); - - expect( - getRegionInfo(mockRegion, { ...mockGetRegionInfoOptions, regionHash: mockRegionHashWithSigningRegion }) - ).toEqual({ - signingService: mockSigningServiceInRegionHash, - hostname: mockHostname, - partition: mockPartition, - }); - - expect(getResolvedHostname).toHaveBeenCalledWith(mockResolvedRegion, { - regionHostname: mockRegionHostname, - partitionHostname: mockPartitionHostname, - }); - expect(getResolvedPartition).toHaveBeenCalledWith(mockRegion, mockGetResolvedPartitionOptions); - expect(getResolvedSigningRegion).toHaveBeenCalledWith(mockHostname, { - regionRegex: mockRegionRegex, - useFipsEndpoint: false, - }); - }); - }); - - it("throws error if hostname is not defined", () => { - (getResolvedHostname as jest.Mock).mockReturnValueOnce(undefined); - const mockRegionHash = getMockRegionHash(RegionCase.REGION); - const mockPartitionHash = getMockPartitionHash(RegionCase.REGION); - expect(() => { - getRegionInfo(mockRegion, { - signingService: mockSigningService, - regionHash: mockRegionHash, - partitionHash: mockPartitionHash, - }); - }).toThrow(); - }); -}); diff --git a/packages/config-resolver/src/regionInfo/getRegionInfo.ts b/packages/config-resolver/src/regionInfo/getRegionInfo.ts deleted file mode 100644 index c6e611878345..000000000000 --- a/packages/config-resolver/src/regionInfo/getRegionInfo.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { RegionInfo } from "@aws-sdk/types"; - -import { getHostnameFromVariants } from "./getHostnameFromVariants"; -import { getResolvedHostname } from "./getResolvedHostname"; -import { getResolvedPartition } from "./getResolvedPartition"; -import { getResolvedSigningRegion } from "./getResolvedSigningRegion"; -import { PartitionHash } from "./PartitionHash"; -import { RegionHash } from "./RegionHash"; - -/** - * @internal - */ -export interface GetRegionInfoOptions { - useFipsEndpoint?: boolean; - useDualstackEndpoint?: boolean; - signingService: string; - regionHash: RegionHash; - partitionHash: PartitionHash; -} - -/** - * @internal - */ -export const getRegionInfo = ( - region: string, - { - useFipsEndpoint = false, - useDualstackEndpoint = false, - signingService, - regionHash, - partitionHash, - }: GetRegionInfoOptions -): RegionInfo => { - const partition = getResolvedPartition(region, { partitionHash }); - const resolvedRegion = region in regionHash ? region : partitionHash[partition]?.endpoint ?? region; - - const hostnameOptions = { useFipsEndpoint, useDualstackEndpoint }; - const regionHostname = getHostnameFromVariants(regionHash[resolvedRegion]?.variants, hostnameOptions); - const partitionHostname = getHostnameFromVariants(partitionHash[partition]?.variants, hostnameOptions); - const hostname = getResolvedHostname(resolvedRegion, { regionHostname, partitionHostname }); - - if (hostname === undefined) { - throw new Error(`Endpoint resolution failed for: ${{ resolvedRegion, useFipsEndpoint, useDualstackEndpoint }}`); - } - - const signingRegion = getResolvedSigningRegion(hostname, { - signingRegion: regionHash[resolvedRegion]?.signingRegion, - regionRegex: partitionHash[partition].regionRegex, - useFipsEndpoint, - }); - - return { - partition, - signingService, - hostname, - ...(signingRegion && { signingRegion }), - ...(regionHash[resolvedRegion]?.signingService && { - signingService: regionHash[resolvedRegion].signingService, - }), - }; -}; diff --git a/packages/config-resolver/src/regionInfo/getResolvedHostname.spec.ts b/packages/config-resolver/src/regionInfo/getResolvedHostname.spec.ts deleted file mode 100644 index e9dc444415ad..000000000000 --- a/packages/config-resolver/src/regionInfo/getResolvedHostname.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getResolvedHostname } from "./getResolvedHostname"; - -describe(getResolvedHostname.name, () => { - const mockRegion = "mockRegion"; - const mockHostname = "{region}.mockHostname.com"; - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("returns hostname if available in regionHostname", () => { - expect( - getResolvedHostname(mockRegion, { - regionHostname: mockHostname, - }) - ).toBe(mockHostname); - }); - - it("returns hostname from partitionHostname when not available in partitionHostname", () => { - expect( - getResolvedHostname(mockRegion, { - partitionHostname: mockHostname, - }) - ).toBe(mockHostname.replace("{region}", mockRegion)); - }); - - it("returns undefined not available in either regionHostname or partitionHostname", () => { - expect(getResolvedHostname(mockRegion, {})).toBeUndefined(); - }); -}); diff --git a/packages/config-resolver/src/regionInfo/getResolvedHostname.ts b/packages/config-resolver/src/regionInfo/getResolvedHostname.ts deleted file mode 100644 index 1c5ac6f75e0a..000000000000 --- a/packages/config-resolver/src/regionInfo/getResolvedHostname.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @internal - */ -export interface GetResolvedHostnameOptions { - regionHostname?: string; - partitionHostname?: string; -} - -/** - * @internal - */ -export const getResolvedHostname = ( - resolvedRegion: string, - { regionHostname, partitionHostname }: GetResolvedHostnameOptions -): string | undefined => - regionHostname - ? regionHostname - : partitionHostname - ? partitionHostname.replace("{region}", resolvedRegion) - : undefined; diff --git a/packages/config-resolver/src/regionInfo/getResolvedPartition.spec.ts b/packages/config-resolver/src/regionInfo/getResolvedPartition.spec.ts deleted file mode 100644 index e6083c419711..000000000000 --- a/packages/config-resolver/src/regionInfo/getResolvedPartition.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { getResolvedPartition } from "./getResolvedPartition"; -import { PartitionHash } from "./PartitionHash"; - -describe(getResolvedPartition.name, () => { - const mockRegion = "mockRegion"; - const mockPartition = "mockPartition"; - const mockHostname = "mockHostname"; - const mockRegionRegex = "mockRegionRegex"; - - it("returns the partition if region is present in partitionHash", () => { - const mockPartitionHash: PartitionHash = { - [mockPartition]: { - regions: [mockRegion, `${mockRegion}2`, `${mockRegion}3`], - regionRegex: mockRegionRegex, - variants: [{ hostname: mockHostname, tags: [] }], - }, - }; - expect(getResolvedPartition(mockRegion, { partitionHash: mockPartitionHash })).toBe(mockPartition); - }); - - it("returns aws if region is not present in any partition", () => { - const mockPartitionHash: PartitionHash = { - [`${mockPartition}2`]: { - regions: [`${mockRegion}2`, `${mockRegion}3`], - regionRegex: mockRegionRegex, - variants: [{ hostname: mockHostname, tags: [] }], - }, - }; - expect(getResolvedPartition(mockRegion, { partitionHash: mockPartitionHash })).toBe("aws"); - }); - - it("returns aws if partitionHash is empty", () => { - expect(getResolvedPartition(mockRegion, { partitionHash: undefined })).toBe("aws"); - }); -}); diff --git a/packages/config-resolver/src/regionInfo/getResolvedPartition.ts b/packages/config-resolver/src/regionInfo/getResolvedPartition.ts deleted file mode 100644 index d0f6e296fdb5..000000000000 --- a/packages/config-resolver/src/regionInfo/getResolvedPartition.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { PartitionHash } from "./PartitionHash"; - -/** - * @internal - */ -export interface GetResolvedPartitionOptions { - partitionHash: PartitionHash; -} - -/** - * @internal - */ -export const getResolvedPartition = (region: string, { partitionHash }: GetResolvedPartitionOptions) => - Object.keys(partitionHash || {}).find((key) => partitionHash[key].regions.includes(region)) ?? "aws"; diff --git a/packages/config-resolver/src/regionInfo/getResolvedSigningRegion.spec.ts b/packages/config-resolver/src/regionInfo/getResolvedSigningRegion.spec.ts deleted file mode 100644 index ce55ef6d69f7..000000000000 --- a/packages/config-resolver/src/regionInfo/getResolvedSigningRegion.spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { getResolvedSigningRegion } from "./getResolvedSigningRegion"; - -describe(getResolvedSigningRegion.name, () => { - const mockSigningRegion = "mockSigningRegion"; - const mockHostname = "mockHostname"; - const mockRegionRegex = "mockRegionRegex"; - - const mockOptions = { - regionRegex: mockRegionRegex, - useFipsEndpoint: false, - }; - - it("returns signingRegion if passed in options", () => { - expect(getResolvedSigningRegion(mockHostname, { ...mockOptions, signingRegion: mockSigningRegion })).toEqual( - mockSigningRegion - ); - }); - - describe("returns undefined if signingRegion is not present and", () => { - it("region is not FIPS", () => { - expect(getResolvedSigningRegion(mockHostname, mockOptions)).not.toBeDefined(); - }); - - it("regionRegex does not return a match in hostname", () => { - const matchSpy = jest.spyOn(String.prototype, "match").mockReturnValueOnce(null); - - expect(getResolvedSigningRegion(mockHostname, { ...mockOptions, useFipsEndpoint: true })).not.toBeDefined(); - expect(matchSpy).toHaveBeenCalledTimes(1); - expect(matchSpy).toHaveBeenCalledWith(mockRegionRegex); - }); - - it("region is not present between dots in a hostname", () => { - const regionInHostname = "us-east-1"; - - expect( - getResolvedSigningRegion(`test-${regionInHostname}.amazonaws.com`, { - ...mockOptions, - regionRegex: "^(us|eu|ap|sa|ca|me|af)\\-\\w+\\-\\d+$", - }) - ).not.toBeDefined(); - }); - }); - - it("returns region from hostname if signingRegion is not present", () => { - const regionInHostname = "us-east-1"; - - expect( - getResolvedSigningRegion(`test.${regionInHostname}.amazonaws.com`, { - ...mockOptions, - regionRegex: "^(us|eu|ap|sa|ca|me|af)\\-\\w+\\-\\d+$", - useFipsEndpoint: true, - }) - ).toEqual(regionInHostname); - }); -}); diff --git a/packages/config-resolver/src/regionInfo/getResolvedSigningRegion.ts b/packages/config-resolver/src/regionInfo/getResolvedSigningRegion.ts deleted file mode 100644 index c518c903dc1d..000000000000 --- a/packages/config-resolver/src/regionInfo/getResolvedSigningRegion.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @internal - */ -export interface GetResolvedSigningRegionOptions { - regionRegex: string; - signingRegion?: string; - useFipsEndpoint: boolean; -} - -/** - * @internal - */ -export const getResolvedSigningRegion = ( - hostname: string, - { signingRegion, regionRegex, useFipsEndpoint }: GetResolvedSigningRegionOptions -) => { - if (signingRegion) { - return signingRegion; - } else if (useFipsEndpoint) { - const regionRegexJs = regionRegex.replace("\\\\", "\\").replace(/^\^/g, "\\.").replace(/\$$/g, "\\."); - const regionRegexmatchArray = hostname.match(regionRegexJs); - if (regionRegexmatchArray) { - return regionRegexmatchArray[0].slice(1, -1); - } - } -}; diff --git a/packages/config-resolver/src/regionInfo/index.ts b/packages/config-resolver/src/regionInfo/index.ts deleted file mode 100644 index 64ef0d51c62a..000000000000 --- a/packages/config-resolver/src/regionInfo/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @internal - */ -export * from "./PartitionHash"; -/** - * @internal - */ -export * from "./RegionHash"; -/** - * @internal - */ -export * from "./getRegionInfo"; diff --git a/packages/credential-provider-imds/jest.config.js b/packages/credential-provider-imds/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/credential-provider-imds/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/credential-provider-imds/package.json b/packages/credential-provider-imds/package.json index 3effd01d7f13..6c268dd7ef6a 100644 --- a/packages/credential-provider-imds/package.json +++ b/packages/credential-provider-imds/package.json @@ -12,7 +12,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "keywords": [ "aws", @@ -24,10 +24,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/node-config-provider": "*", - "@aws-sdk/property-provider": "*", - "@aws-sdk/types": "*", - "@aws-sdk/url-parser": "*", + "@smithy/credential-provider-imds": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/credential-provider-imds/src/config/Endpoint.ts b/packages/credential-provider-imds/src/config/Endpoint.ts deleted file mode 100644 index a02276be6b2d..000000000000 --- a/packages/credential-provider-imds/src/config/Endpoint.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @internal - */ -export enum Endpoint { - IPv4 = "http://169.254.169.254", - IPv6 = "http://[fd00:ec2::254]", -} diff --git a/packages/credential-provider-imds/src/config/EndpointConfigOptions.spec.ts b/packages/credential-provider-imds/src/config/EndpointConfigOptions.spec.ts deleted file mode 100644 index 53e8815bbc97..000000000000 --- a/packages/credential-provider-imds/src/config/EndpointConfigOptions.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { CONFIG_ENDPOINT_NAME, ENDPOINT_CONFIG_OPTIONS, ENV_ENDPOINT_NAME } from "./EndpointConfigOptions"; - -describe("ENDPOINT_CONFIG_OPTIONS", () => { - describe("environmentVariableSelector", () => { - const { environmentVariableSelector } = ENDPOINT_CONFIG_OPTIONS; - it.each([undefined, "mockEndpoint"])(`when env[${ENV_ENDPOINT_NAME}]: %s`, (mockEndpoint) => { - expect(environmentVariableSelector({ [ENV_ENDPOINT_NAME]: mockEndpoint })).toBe(mockEndpoint); - }); - }); - - describe("configFileSelector", () => { - const { configFileSelector } = ENDPOINT_CONFIG_OPTIONS; - it.each([undefined, "mockEndpoint"])(`when env[${CONFIG_ENDPOINT_NAME}]: %s`, (mockEndpoint) => { - expect(configFileSelector({ [CONFIG_ENDPOINT_NAME]: mockEndpoint })).toBe(mockEndpoint); - }); - }); - - it("default returns undefined", () => { - const { default: defaultKey } = ENDPOINT_CONFIG_OPTIONS; - expect(defaultKey).toBe(undefined); - }); -}); diff --git a/packages/credential-provider-imds/src/config/EndpointConfigOptions.ts b/packages/credential-provider-imds/src/config/EndpointConfigOptions.ts deleted file mode 100644 index 93e65af886d3..000000000000 --- a/packages/credential-provider-imds/src/config/EndpointConfigOptions.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { LoadedConfigSelectors } from "@aws-sdk/node-config-provider"; - -/** - * @internal - */ -export const ENV_ENDPOINT_NAME = "AWS_EC2_METADATA_SERVICE_ENDPOINT"; -/** - * @internal - */ -export const CONFIG_ENDPOINT_NAME = "ec2_metadata_service_endpoint"; - -/** - * @internal - */ -export const ENDPOINT_CONFIG_OPTIONS: LoadedConfigSelectors = { - environmentVariableSelector: (env) => env[ENV_ENDPOINT_NAME], - configFileSelector: (profile) => profile[CONFIG_ENDPOINT_NAME], - default: undefined, -}; diff --git a/packages/credential-provider-imds/src/config/EndpointMode.ts b/packages/credential-provider-imds/src/config/EndpointMode.ts deleted file mode 100644 index 695469be9b30..000000000000 --- a/packages/credential-provider-imds/src/config/EndpointMode.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @internal - */ -export enum EndpointMode { - IPv4 = "IPv4", - IPv6 = "IPv6", -} diff --git a/packages/credential-provider-imds/src/config/EndpointModeConfigOptions.spec.ts b/packages/credential-provider-imds/src/config/EndpointModeConfigOptions.spec.ts deleted file mode 100644 index 130fd44f0aee..000000000000 --- a/packages/credential-provider-imds/src/config/EndpointModeConfigOptions.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { EndpointMode } from "./EndpointMode"; -import { - CONFIG_ENDPOINT_MODE_NAME, - ENDPOINT_MODE_CONFIG_OPTIONS, - ENV_ENDPOINT_MODE_NAME, -} from "./EndpointModeConfigOptions"; - -describe("ENDPOINT_MODE_CONFIG_OPTIONS", () => { - describe("environmentVariableSelector", () => { - const { environmentVariableSelector } = ENDPOINT_MODE_CONFIG_OPTIONS; - it.each([undefined, "mockEndpointMode"])(`when env[${ENV_ENDPOINT_MODE_NAME}]: %s`, (mockEndpoint) => { - expect(environmentVariableSelector({ [ENV_ENDPOINT_MODE_NAME]: mockEndpoint })).toBe(mockEndpoint); - }); - }); - - describe("configFileSelector", () => { - const { configFileSelector } = ENDPOINT_MODE_CONFIG_OPTIONS; - it.each([undefined, "mockEndpointMode"])(`when env[${CONFIG_ENDPOINT_MODE_NAME}]: %s`, (mockEndpoint) => { - expect(configFileSelector({ [CONFIG_ENDPOINT_MODE_NAME]: mockEndpoint })).toBe(mockEndpoint); - }); - }); - - it(`default returns ${EndpointMode.IPv4}`, () => { - const { default: defaultKey } = ENDPOINT_MODE_CONFIG_OPTIONS; - expect(defaultKey).toBe(EndpointMode.IPv4); - }); -}); diff --git a/packages/credential-provider-imds/src/config/EndpointModeConfigOptions.ts b/packages/credential-provider-imds/src/config/EndpointModeConfigOptions.ts deleted file mode 100644 index b5264278c5c7..000000000000 --- a/packages/credential-provider-imds/src/config/EndpointModeConfigOptions.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { LoadedConfigSelectors } from "@aws-sdk/node-config-provider"; - -import { EndpointMode } from "./EndpointMode"; - -/** - * @internal - */ -export const ENV_ENDPOINT_MODE_NAME = "AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE"; -/** - * @internal - */ -export const CONFIG_ENDPOINT_MODE_NAME = "ec2_metadata_service_endpoint_mode"; - -/** - * @internal - */ -export const ENDPOINT_MODE_CONFIG_OPTIONS: LoadedConfigSelectors = { - environmentVariableSelector: (env) => env[ENV_ENDPOINT_MODE_NAME], - configFileSelector: (profile) => profile[CONFIG_ENDPOINT_MODE_NAME], - default: EndpointMode.IPv4, -}; diff --git a/packages/credential-provider-imds/src/fromContainerMetadata.spec.ts b/packages/credential-provider-imds/src/fromContainerMetadata.spec.ts deleted file mode 100644 index 0ff8830e12a4..000000000000 --- a/packages/credential-provider-imds/src/fromContainerMetadata.spec.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { - ENV_CMDS_AUTH_TOKEN, - ENV_CMDS_FULL_URI, - ENV_CMDS_RELATIVE_URI, - fromContainerMetadata, -} from "./fromContainerMetadata"; -import { httpRequest } from "./remoteProvider/httpRequest"; -import { fromImdsCredentials, ImdsCredentials } from "./remoteProvider/ImdsCredentials"; - -const mockHttpRequest = httpRequest; -jest.mock("./remoteProvider/httpRequest"); - -const relativeUri = process.env[ENV_CMDS_RELATIVE_URI]; -const fullUri = process.env[ENV_CMDS_FULL_URI]; -const authToken = process.env[ENV_CMDS_AUTH_TOKEN]; - -beforeEach(() => { - mockHttpRequest.mockReset(); - delete process.env[ENV_CMDS_RELATIVE_URI]; - delete process.env[ENV_CMDS_FULL_URI]; - delete process.env[ENV_CMDS_AUTH_TOKEN]; -}); - -afterAll(() => { - process.env[ENV_CMDS_RELATIVE_URI] = relativeUri; - process.env[ENV_CMDS_FULL_URI] = fullUri; - process.env[ENV_CMDS_AUTH_TOKEN] = authToken; -}); - -describe("fromContainerMetadata", () => { - const creds: ImdsCredentials = Object.freeze({ - AccessKeyId: "foo", - SecretAccessKey: "bar", - Token: "baz", - Expiration: new Date().toISOString(), - }); - - it("should reject the promise with a terminal error if the container credentials environment variable is not set", async () => { - await fromContainerMetadata()().then( - () => { - throw new Error("The promise should have been rejected"); - }, - (err) => { - expect((err as any).tryNextLink).toBeFalsy(); - } - ); - }); - - it(`should inject an authorization header containing the contents of the ${ENV_CMDS_AUTH_TOKEN} environment variable if defined`, async () => { - const token = "Basic abcd"; - process.env[ENV_CMDS_FULL_URI] = "http://localhost:8080/path"; - process.env[ENV_CMDS_AUTH_TOKEN] = token; - mockHttpRequest.mockReturnValue(Promise.resolve(JSON.stringify(creds))); - - await fromContainerMetadata()(); - - expect(mockHttpRequest.mock.calls.length).toBe(1); - const [options = {}] = mockHttpRequest.mock.calls[0]; - expect(options.headers).toMatchObject({ - Authorization: token, - }); - }); - - describe(ENV_CMDS_RELATIVE_URI, () => { - beforeEach(() => { - process.env[ENV_CMDS_RELATIVE_URI] = "/relative/uri"; - }); - - it("should resolve credentials by fetching them from the container metadata service", async () => { - mockHttpRequest.mockReturnValue(Promise.resolve(JSON.stringify(creds))); - - expect(await fromContainerMetadata()()).toEqual(fromImdsCredentials(creds)); - }); - - it("should retry the fetching operation up to maxRetries times", async () => { - const maxRetries = 5; - for (let i = 0; i < maxRetries - 1; i++) { - mockHttpRequest.mockReturnValueOnce(Promise.reject("No!")); - } - mockHttpRequest.mockReturnValueOnce(Promise.resolve(JSON.stringify(creds))); - - expect(await fromContainerMetadata({ maxRetries })()).toEqual(fromImdsCredentials(creds)); - expect(mockHttpRequest.mock.calls.length).toEqual(maxRetries); - }); - - it("should retry responses that receive invalid response values", async () => { - for (const key of Object.keys(creds)) { - const invalidCreds: any = { ...creds }; - delete invalidCreds[key]; - mockHttpRequest.mockReturnValueOnce(Promise.resolve(JSON.stringify(invalidCreds))); - } - mockHttpRequest.mockReturnValueOnce(Promise.resolve(JSON.stringify(creds))); - - await fromContainerMetadata({ maxRetries: 100 })(); - expect(mockHttpRequest.mock.calls.length).toEqual(Object.keys(creds).length + 1); - }); - - it("should pass relevant configuration to httpRequest", async () => { - const timeout = Math.ceil(Math.random() * 1000); - mockHttpRequest.mockReturnValue(Promise.resolve(JSON.stringify(creds))); - await fromContainerMetadata({ timeout })(); - expect(mockHttpRequest.mock.calls.length).toEqual(1); - expect(mockHttpRequest.mock.calls[0][0]).toEqual({ - hostname: "169.254.170.2", - path: process.env[ENV_CMDS_RELATIVE_URI], - timeout, - }); - }); - }); - - describe(ENV_CMDS_FULL_URI, () => { - it("should pass relevant configuration to httpRequest", async () => { - process.env[ENV_CMDS_FULL_URI] = "http://localhost:8080/path"; - - const timeout = Math.ceil(Math.random() * 1000); - mockHttpRequest.mockReturnValue(Promise.resolve(JSON.stringify(creds))); - await fromContainerMetadata({ timeout })(); - expect(mockHttpRequest.mock.calls.length).toEqual(1); - const { protocol, hostname, path, port, timeout: actualTimeout } = mockHttpRequest.mock.calls[0][0]; - expect(protocol).toBe("http:"); - expect(hostname).toBe("localhost"); - expect(path).toBe("/path"); - expect(port).toBe(8080); - expect(actualTimeout).toBe(timeout); - }); - - it(`should prefer ${ENV_CMDS_RELATIVE_URI} to ${ENV_CMDS_FULL_URI}`, async () => { - process.env[ENV_CMDS_RELATIVE_URI] = "foo"; - process.env[ENV_CMDS_FULL_URI] = "http://localhost:8080/path"; - - const timeout = Math.ceil(Math.random() * 1000); - mockHttpRequest.mockReturnValue(Promise.resolve(JSON.stringify(creds))); - await fromContainerMetadata({ timeout })(); - expect(mockHttpRequest.mock.calls.length).toEqual(1); - expect(mockHttpRequest.mock.calls[0][0]).toEqual({ - hostname: "169.254.170.2", - path: "foo", - timeout, - }); - }); - - it("should reject the promise with a terminal error if a unexpected protocol is specified", async () => { - process.env[ENV_CMDS_FULL_URI] = "wss://localhost:8080/path"; - - await fromContainerMetadata()().then( - () => { - throw new Error("The promise should have been rejected"); - }, - (err) => { - expect((err as any).tryNextLink).toBeFalsy(); - } - ); - }); - - it("should reject the promise with a terminal error if a unexpected hostname is specified", async () => { - process.env[ENV_CMDS_FULL_URI] = "https://bucket.s3.amazonaws.com/key"; - - await fromContainerMetadata()().then( - () => { - throw new Error("The promise should have been rejected"); - }, - (err) => { - expect((err as any).tryNextLink).toBeFalsy(); - } - ); - }); - }); -}); diff --git a/packages/credential-provider-imds/src/fromContainerMetadata.ts b/packages/credential-provider-imds/src/fromContainerMetadata.ts deleted file mode 100644 index 427ec1d9180d..000000000000 --- a/packages/credential-provider-imds/src/fromContainerMetadata.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { CredentialsProviderError } from "@aws-sdk/property-provider"; -import { AwsCredentialIdentityProvider } from "@aws-sdk/types"; -import { RequestOptions } from "http"; -import { parse } from "url"; - -import { httpRequest } from "./remoteProvider/httpRequest"; -import { fromImdsCredentials, isImdsCredentials } from "./remoteProvider/ImdsCredentials"; -import { providerConfigFromInit, RemoteProviderInit } from "./remoteProvider/RemoteProviderInit"; -import { retry } from "./remoteProvider/retry"; - -/** - * @internal - */ -export const ENV_CMDS_FULL_URI = "AWS_CONTAINER_CREDENTIALS_FULL_URI"; -/** - * @internal - */ -export const ENV_CMDS_RELATIVE_URI = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"; -/** - * @internal - */ -export const ENV_CMDS_AUTH_TOKEN = "AWS_CONTAINER_AUTHORIZATION_TOKEN"; - -/** - * @internal - * - * Creates a credential provider that will source credentials from the ECS - * Container Metadata Service - */ -export const fromContainerMetadata = (init: RemoteProviderInit = {}): AwsCredentialIdentityProvider => { - const { timeout, maxRetries } = providerConfigFromInit(init); - return () => - retry(async () => { - const requestOptions = await getCmdsUri(); - const credsResponse = JSON.parse(await requestFromEcsImds(timeout, requestOptions)); - if (!isImdsCredentials(credsResponse)) { - throw new CredentialsProviderError("Invalid response received from instance metadata service."); - } - return fromImdsCredentials(credsResponse); - }, maxRetries); -}; - -const requestFromEcsImds = async (timeout: number, options: RequestOptions): Promise => { - if (process.env[ENV_CMDS_AUTH_TOKEN]) { - options.headers = { - ...options.headers, - Authorization: process.env[ENV_CMDS_AUTH_TOKEN], - }; - } - - const buffer = await httpRequest({ - ...options, - timeout, - }); - return buffer.toString(); -}; - -const CMDS_IP = "169.254.170.2"; -const GREENGRASS_HOSTS = { - localhost: true, - "127.0.0.1": true, -}; -const GREENGRASS_PROTOCOLS = { - "http:": true, - "https:": true, -}; - -const getCmdsUri = async (): Promise => { - if (process.env[ENV_CMDS_RELATIVE_URI]) { - return { - hostname: CMDS_IP, - path: process.env[ENV_CMDS_RELATIVE_URI], - }; - } - - if (process.env[ENV_CMDS_FULL_URI]) { - const parsed = parse(process.env[ENV_CMDS_FULL_URI]!); - if (!parsed.hostname || !(parsed.hostname in GREENGRASS_HOSTS)) { - throw new CredentialsProviderError( - `${parsed.hostname} is not a valid container metadata service hostname`, - false - ); - } - - if (!parsed.protocol || !(parsed.protocol in GREENGRASS_PROTOCOLS)) { - throw new CredentialsProviderError( - `${parsed.protocol} is not a valid container metadata service protocol`, - false - ); - } - - return { - ...parsed, - port: parsed.port ? parseInt(parsed.port, 10) : undefined, - }; - } - - throw new CredentialsProviderError( - "The container metadata credential provider cannot be used unless" + - ` the ${ENV_CMDS_RELATIVE_URI} or ${ENV_CMDS_FULL_URI} environment` + - " variable is set", - false - ); -}; diff --git a/packages/credential-provider-imds/src/fromInstanceMetadata.spec.ts b/packages/credential-provider-imds/src/fromInstanceMetadata.spec.ts deleted file mode 100644 index 0fb8707b039e..000000000000 --- a/packages/credential-provider-imds/src/fromInstanceMetadata.spec.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { CredentialsProviderError } from "@aws-sdk/property-provider"; - -import { fromInstanceMetadata } from "./fromInstanceMetadata"; -import { httpRequest } from "./remoteProvider/httpRequest"; -import { fromImdsCredentials, isImdsCredentials } from "./remoteProvider/ImdsCredentials"; -import { providerConfigFromInit } from "./remoteProvider/RemoteProviderInit"; -import { retry } from "./remoteProvider/retry"; -import { getInstanceMetadataEndpoint } from "./utils/getInstanceMetadataEndpoint"; -import { staticStabilityProvider } from "./utils/staticStabilityProvider"; - -jest.mock("./remoteProvider/httpRequest"); -jest.mock("./remoteProvider/ImdsCredentials"); -jest.mock("./remoteProvider/retry"); -jest.mock("./remoteProvider/RemoteProviderInit"); -jest.mock("./utils/getInstanceMetadataEndpoint"); -jest.mock("./utils/staticStabilityProvider"); - -describe("fromInstanceMetadata", () => { - const hostname = "127.0.0.1"; - const mockTimeout = 1000; - const mockMaxRetries = 3; - const mockToken = "fooToken"; - const mockProfile = "fooProfile"; - - const mockTokenRequestOptions = { - hostname, - path: "/latest/api/token", - method: "PUT", - headers: { - "x-aws-ec2-metadata-token-ttl-seconds": "21600", - }, - timeout: mockTimeout, - }; - - const mockProfileRequestOptions = { - hostname, - path: "/latest/meta-data/iam/security-credentials/", - timeout: mockTimeout, - headers: { - "x-aws-ec2-metadata-token": mockToken, - }, - }; - - const ONE_HOUR_IN_FUTURE = new Date(Date.now() + 60 * 60 * 1000); - const mockImdsCreds = Object.freeze({ - AccessKeyId: "foo", - SecretAccessKey: "bar", - Token: "baz", - Expiration: ONE_HOUR_IN_FUTURE.toISOString(), - }); - - const mockCreds = Object.freeze({ - accessKeyId: mockImdsCreds.AccessKeyId, - secretAccessKey: mockImdsCreds.SecretAccessKey, - sessionToken: mockImdsCreds.Token, - expiration: new Date(mockImdsCreds.Expiration), - }); - - beforeEach(() => { - (staticStabilityProvider as jest.Mock).mockImplementation((input) => input); - (getInstanceMetadataEndpoint as jest.Mock).mockResolvedValue({ hostname }); - (isImdsCredentials as unknown as jest.Mock).mockReturnValue(true); - (providerConfigFromInit as jest.Mock).mockReturnValue({ - timeout: mockTimeout, - maxRetries: mockMaxRetries, - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it("gets token and profile name to fetch credentials", async () => { - (httpRequest as jest.Mock) - .mockResolvedValueOnce(mockToken) - .mockResolvedValueOnce(mockProfile) - .mockResolvedValueOnce(JSON.stringify(mockImdsCreds)); - - (retry as jest.Mock).mockImplementation((fn: any) => fn()); - (fromImdsCredentials as jest.Mock).mockReturnValue(mockCreds); - - await expect(fromInstanceMetadata()()).resolves.toEqual(mockCreds); - expect(httpRequest).toHaveBeenCalledTimes(3); - expect(httpRequest).toHaveBeenNthCalledWith(1, mockTokenRequestOptions); - expect(httpRequest).toHaveBeenNthCalledWith(2, mockProfileRequestOptions); - expect(httpRequest).toHaveBeenNthCalledWith(3, { - ...mockProfileRequestOptions, - path: `${mockProfileRequestOptions.path}${mockProfile}`, - }); - }); - - it("trims profile returned name from IMDS", async () => { - (httpRequest as jest.Mock) - .mockResolvedValueOnce(mockToken) - .mockResolvedValueOnce(" " + mockProfile + " ") - .mockResolvedValueOnce(JSON.stringify(mockImdsCreds)); - - (retry as jest.Mock).mockImplementation((fn: any) => fn()); - (fromImdsCredentials as jest.Mock).mockReturnValue(mockCreds); - - await expect(fromInstanceMetadata()()).resolves.toEqual(mockCreds); - expect(httpRequest).toHaveBeenNthCalledWith(3, { - ...mockProfileRequestOptions, - path: `${mockProfileRequestOptions.path}${mockProfile}`, - }); - }); - - it("passes {} to providerConfigFromInit if init not defined", async () => { - (retry as jest.Mock).mockResolvedValueOnce(mockProfile).mockResolvedValueOnce(mockCreds); - - await expect(fromInstanceMetadata()()).resolves.toEqual(mockCreds); - expect(providerConfigFromInit).toHaveBeenCalledTimes(1); - expect(providerConfigFromInit).toHaveBeenCalledWith({}); - }); - - it("passes init to providerConfigFromInit", async () => { - (retry as jest.Mock).mockResolvedValueOnce(mockProfile).mockResolvedValueOnce(mockCreds); - - const init = { maxRetries: 5, timeout: 1213 }; - await expect(fromInstanceMetadata(init)()).resolves.toEqual(mockCreds); - expect(providerConfigFromInit).toHaveBeenCalledTimes(1); - expect(providerConfigFromInit).toHaveBeenCalledWith(init); - }); - - it("passes maxRetries returned from providerConfigFromInit to retry", async () => { - (retry as jest.Mock).mockResolvedValueOnce(mockProfile).mockResolvedValueOnce(mockCreds); - - await expect(fromInstanceMetadata()()).resolves.toEqual(mockCreds); - expect(retry).toHaveBeenCalledTimes(2); - expect((retry as jest.Mock).mock.calls[0][1]).toBe(mockMaxRetries); - expect((retry as jest.Mock).mock.calls[1][1]).toBe(mockMaxRetries); - }); - - it("throws CredentialsProviderError if credentials returned are incorrect", async () => { - (httpRequest as jest.Mock) - .mockResolvedValueOnce(mockToken) - .mockResolvedValueOnce(mockProfile) - .mockResolvedValueOnce(JSON.stringify(mockImdsCreds)); - - (retry as jest.Mock).mockImplementation((fn: any) => fn()); - (isImdsCredentials as unknown as jest.Mock).mockReturnValueOnce(false); - - await expect(fromInstanceMetadata()()).rejects.toEqual( - new CredentialsProviderError("Invalid response received from instance metadata service.") - ); - expect(retry).toHaveBeenCalledTimes(2); - expect(httpRequest).toHaveBeenCalledTimes(3); - expect(isImdsCredentials).toHaveBeenCalledTimes(1); - expect(isImdsCredentials).toHaveBeenCalledWith(mockImdsCreds); - expect(fromImdsCredentials).not.toHaveBeenCalled(); - }); - - it("throws Error if httpRequest for profile fails", async () => { - const mockError = new Error("profile not found"); - (httpRequest as jest.Mock).mockResolvedValueOnce(mockToken).mockRejectedValueOnce(mockError); - (retry as jest.Mock).mockImplementation((fn: any) => fn()); - - await expect(fromInstanceMetadata()()).rejects.toEqual(mockError); - expect(retry).toHaveBeenCalledTimes(1); - expect(httpRequest).toHaveBeenCalledTimes(2); - }); - - it("throws Error if httpRequest for credentials fails", async () => { - const mockError = new Error("creds not found"); - (httpRequest as jest.Mock) - .mockResolvedValueOnce(mockToken) - .mockResolvedValueOnce(mockProfile) - .mockRejectedValueOnce(mockError); - (retry as jest.Mock).mockImplementation((fn: any) => fn()); - - await expect(fromInstanceMetadata()()).rejects.toEqual(mockError); - expect(retry).toHaveBeenCalledTimes(2); - expect(httpRequest).toHaveBeenCalledTimes(3); - expect(fromImdsCredentials).not.toHaveBeenCalled(); - }); - - it("throws SyntaxError if httpRequest returns unparseable creds", async () => { - (httpRequest as jest.Mock) - .mockResolvedValueOnce(mockToken) - .mockResolvedValueOnce(mockProfile) - .mockResolvedValueOnce("."); - (retry as jest.Mock).mockImplementation((fn: any) => fn()); - - await expect(fromInstanceMetadata()()).rejects.toThrow("Unexpected token"); - expect(retry).toHaveBeenCalledTimes(2); - expect(httpRequest).toHaveBeenCalledTimes(3); - expect(fromImdsCredentials).not.toHaveBeenCalled(); - }); - - it("throws error if metadata token errors with statusCode 400", async () => { - const tokenError = Object.assign(new Error("token not found"), { - statusCode: 400, - }); - (httpRequest as jest.Mock).mockRejectedValueOnce(tokenError); - - await expect(fromInstanceMetadata()()).rejects.toEqual(tokenError); - }); - - it("should call staticStabilityProvider with the credential loader", async () => { - (httpRequest as jest.Mock) - .mockResolvedValueOnce(mockToken) - .mockResolvedValueOnce(mockProfile) - .mockResolvedValueOnce(JSON.stringify(mockImdsCreds)); - - (retry as jest.Mock).mockImplementation((fn: any) => fn()); - (fromImdsCredentials as jest.Mock).mockReturnValue(mockCreds); - - await fromInstanceMetadata()(); - expect(staticStabilityProvider as jest.Mock).toBeCalledTimes(1); - }); - - describe("disables fetching of token", () => { - beforeEach(() => { - (retry as jest.Mock).mockImplementation((fn: any) => fn()); - (fromImdsCredentials as jest.Mock).mockReturnValue(mockCreds); - }); - - it("when token fetch returns with TimeoutError", async () => { - const tokenError = new Error("TimeoutError"); - - (httpRequest as jest.Mock) - .mockRejectedValueOnce(tokenError) - .mockResolvedValueOnce(mockProfile) - .mockResolvedValueOnce(JSON.stringify(mockImdsCreds)) - .mockResolvedValueOnce(mockProfile) - .mockResolvedValueOnce(JSON.stringify(mockImdsCreds)); - - const fromInstanceMetadataFunc = fromInstanceMetadata(); - await expect(fromInstanceMetadataFunc()).resolves.toEqual(mockCreds); - await expect(fromInstanceMetadataFunc()).resolves.toEqual(mockCreds); - }); - - [403, 404, 405].forEach((statusCode) => { - it(`when token fetch errors with statusCode ${statusCode}`, async () => { - const tokenError = Object.assign(new Error(), { statusCode }); - - (httpRequest as jest.Mock) - .mockRejectedValueOnce(tokenError) - .mockResolvedValueOnce(mockProfile) - .mockResolvedValueOnce(JSON.stringify(mockImdsCreds)) - .mockResolvedValueOnce(mockProfile) - .mockResolvedValueOnce(JSON.stringify(mockImdsCreds)); - - const fromInstanceMetadataFunc = fromInstanceMetadata(); - await expect(fromInstanceMetadataFunc()).resolves.toEqual(mockCreds); - await expect(fromInstanceMetadataFunc()).resolves.toEqual(mockCreds); - }); - }); - }); - - it("uses insecure data flow once, if error is not TimeoutError", async () => { - const tokenError = new Error("Error"); - - (httpRequest as jest.Mock) - .mockRejectedValueOnce(tokenError) - .mockResolvedValueOnce(mockProfile) - .mockResolvedValueOnce(JSON.stringify(mockImdsCreds)) - .mockResolvedValueOnce(mockToken) - .mockResolvedValueOnce(mockProfile) - .mockResolvedValueOnce(JSON.stringify(mockImdsCreds)); - - (retry as jest.Mock).mockImplementation((fn: any) => fn()); - (fromImdsCredentials as jest.Mock).mockReturnValue(mockCreds); - - const fromInstanceMetadataFunc = fromInstanceMetadata(); - await expect(fromInstanceMetadataFunc()).resolves.toEqual(mockCreds); - await expect(fromInstanceMetadataFunc()).resolves.toEqual(mockCreds); - }); - - it("uses insecure data flow once, if error statusCode is not 400, 403, 404, 405", async () => { - const tokenError = Object.assign(new Error("Error"), { statusCode: 406 }); - - (httpRequest as jest.Mock) - .mockRejectedValueOnce(tokenError) - .mockResolvedValueOnce(mockProfile) - .mockResolvedValueOnce(JSON.stringify(mockImdsCreds)) - .mockResolvedValueOnce(mockToken) - .mockResolvedValueOnce(mockProfile) - .mockResolvedValueOnce(JSON.stringify(mockImdsCreds)); - - (retry as jest.Mock).mockImplementation((fn: any) => fn()); - (fromImdsCredentials as jest.Mock).mockReturnValue(mockCreds); - - const fromInstanceMetadataFunc = fromInstanceMetadata(); - await expect(fromInstanceMetadataFunc()).resolves.toEqual(mockCreds); - await expect(fromInstanceMetadataFunc()).resolves.toEqual(mockCreds); - }); -}); diff --git a/packages/credential-provider-imds/src/fromInstanceMetadata.ts b/packages/credential-provider-imds/src/fromInstanceMetadata.ts deleted file mode 100644 index 15ac566be4d5..000000000000 --- a/packages/credential-provider-imds/src/fromInstanceMetadata.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { CredentialsProviderError } from "@aws-sdk/property-provider"; -import { AwsCredentialIdentity, Provider } from "@aws-sdk/types"; -import { RequestOptions } from "http"; - -import { httpRequest } from "./remoteProvider/httpRequest"; -import { fromImdsCredentials, isImdsCredentials } from "./remoteProvider/ImdsCredentials"; -import { providerConfigFromInit, RemoteProviderInit } from "./remoteProvider/RemoteProviderInit"; -import { retry } from "./remoteProvider/retry"; -import { InstanceMetadataCredentials } from "./types"; -import { getInstanceMetadataEndpoint } from "./utils/getInstanceMetadataEndpoint"; -import { staticStabilityProvider } from "./utils/staticStabilityProvider"; - -const IMDS_PATH = "/latest/meta-data/iam/security-credentials/"; -const IMDS_TOKEN_PATH = "/latest/api/token"; - -/** - * @internal - * - * Creates a credential provider that will source credentials from the EC2 - * Instance Metadata Service - */ -export const fromInstanceMetadata = (init: RemoteProviderInit = {}): Provider => - staticStabilityProvider(getInstanceImdsProvider(init), { logger: init.logger }); - -const getInstanceImdsProvider = (init: RemoteProviderInit) => { - // when set to true, metadata service will not fetch token - let disableFetchToken = false; - const { timeout, maxRetries } = providerConfigFromInit(init); - - const getCredentials = async (maxRetries: number, options: RequestOptions) => { - const profile = ( - await retry(async () => { - let profile: string; - try { - profile = await getProfile(options); - } catch (err) { - if (err.statusCode === 401) { - disableFetchToken = false; - } - throw err; - } - return profile; - }, maxRetries) - ).trim(); - - return retry(async () => { - let creds: AwsCredentialIdentity; - try { - creds = await getCredentialsFromProfile(profile, options); - } catch (err) { - if (err.statusCode === 401) { - disableFetchToken = false; - } - throw err; - } - return creds; - }, maxRetries); - }; - - return async () => { - const endpoint = await getInstanceMetadataEndpoint(); - if (disableFetchToken) { - return getCredentials(maxRetries, { ...endpoint, timeout }); - } else { - let token: string; - try { - token = (await getMetadataToken({ ...endpoint, timeout })).toString(); - } catch (error) { - if (error?.statusCode === 400) { - throw Object.assign(error, { - message: "EC2 Metadata token request returned error", - }); - } else if (error.message === "TimeoutError" || [403, 404, 405].includes(error.statusCode)) { - disableFetchToken = true; - } - return getCredentials(maxRetries, { ...endpoint, timeout }); - } - return getCredentials(maxRetries, { - ...endpoint, - headers: { - "x-aws-ec2-metadata-token": token, - }, - timeout, - }); - } - }; -}; - -const getMetadataToken = async (options: RequestOptions) => - httpRequest({ - ...options, - path: IMDS_TOKEN_PATH, - method: "PUT", - headers: { - "x-aws-ec2-metadata-token-ttl-seconds": "21600", - }, - }); - -const getProfile = async (options: RequestOptions) => (await httpRequest({ ...options, path: IMDS_PATH })).toString(); - -const getCredentialsFromProfile = async (profile: string, options: RequestOptions) => { - const credsResponse = JSON.parse( - ( - await httpRequest({ - ...options, - path: IMDS_PATH + profile, - }) - ).toString() - ); - - if (!isImdsCredentials(credsResponse)) { - throw new CredentialsProviderError("Invalid response received from instance metadata service."); - } - - return fromImdsCredentials(credsResponse); -}; diff --git a/packages/credential-provider-imds/src/index.ts b/packages/credential-provider-imds/src/index.ts index 93ee04a2f7bf..bb2dd619c958 100644 --- a/packages/credential-provider-imds/src/index.ts +++ b/packages/credential-provider-imds/src/index.ts @@ -1,24 +1 @@ -/** - * @internal - */ -export * from "./fromContainerMetadata"; -/** - * @internal - */ -export * from "./fromInstanceMetadata"; -/** - * @internal - */ -export * from "./remoteProvider/RemoteProviderInit"; -/** - * @internal - */ -export * from "./types"; -/** - * @internal - */ -export { httpRequest } from "./remoteProvider/httpRequest"; -/** - * @internal - */ -export { getInstanceMetadataEndpoint } from "./utils/getInstanceMetadataEndpoint"; +export * from "@smithy/credential-provider-imds"; diff --git a/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.spec.ts b/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.spec.ts deleted file mode 100644 index e8484ec1260a..000000000000 --- a/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { AwsCredentialIdentity } from "@aws-sdk/types"; - -import { fromImdsCredentials, ImdsCredentials, isImdsCredentials } from "./ImdsCredentials"; - -const creds: ImdsCredentials = Object.freeze({ - AccessKeyId: "foo", - SecretAccessKey: "bar", - Token: "baz", - Expiration: new Date().toISOString(), -}); - -describe("isImdsCredentials", () => { - it("should accept valid ImdsCredentials objects", () => { - expect(isImdsCredentials(creds)).toBe(true); - }); - - it("should reject credentials without an AccessKeyId", () => { - expect(isImdsCredentials({ ...creds, AccessKeyId: void 0 })).toBe(false); - }); - - it("should reject credentials without a SecretAccessKey", () => { - expect(isImdsCredentials({ ...creds, SecretAccessKey: void 0 })).toBe(false); - }); - - it("should reject credentials without a Token", () => { - expect(isImdsCredentials({ ...creds, Token: void 0 })).toBe(false); - }); - - it("should reject credentials without an Expiration", () => { - expect(isImdsCredentials({ ...creds, Expiration: void 0 })).toBe(false); - }); - - it("should reject scalar values", () => { - for (const scalar of ["string", 1, true, null, void 0]) { - expect(isImdsCredentials(scalar)).toBe(false); - } - }); -}); - -describe("fromImdsCredentials", () => { - it("should convert IMDS credentials to a credentials object", () => { - const converted: AwsCredentialIdentity = fromImdsCredentials(creds); - expect(converted.accessKeyId).toEqual(creds.AccessKeyId); - expect(converted.secretAccessKey).toEqual(creds.SecretAccessKey); - expect(converted.sessionToken).toEqual(creds.Token); - expect(converted.expiration).toEqual(new Date(creds.Expiration)); - }); -}); diff --git a/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.ts b/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.ts deleted file mode 100644 index afa0fd19fd42..000000000000 --- a/packages/credential-provider-imds/src/remoteProvider/ImdsCredentials.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { AwsCredentialIdentity } from "@aws-sdk/types"; - -/** - * @internal - */ -export interface ImdsCredentials { - AccessKeyId: string; - SecretAccessKey: string; - Token: string; - Expiration: string; -} - -/** - * @internal - */ -export const isImdsCredentials = (arg: any): arg is ImdsCredentials => - Boolean(arg) && - typeof arg === "object" && - typeof arg.AccessKeyId === "string" && - typeof arg.SecretAccessKey === "string" && - typeof arg.Token === "string" && - typeof arg.Expiration === "string"; - -/** - * @internal - */ -export const fromImdsCredentials = (creds: ImdsCredentials): AwsCredentialIdentity => ({ - accessKeyId: creds.AccessKeyId, - secretAccessKey: creds.SecretAccessKey, - sessionToken: creds.Token, - expiration: new Date(creds.Expiration), -}); diff --git a/packages/credential-provider-imds/src/remoteProvider/RemoteProviderInit.spec.ts b/packages/credential-provider-imds/src/remoteProvider/RemoteProviderInit.spec.ts deleted file mode 100644 index c688dd5eb568..000000000000 --- a/packages/credential-provider-imds/src/remoteProvider/RemoteProviderInit.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, providerConfigFromInit } from "./RemoteProviderInit"; - -describe("providerConfigFromInit", () => { - it("should populate default values for retries and timeouts", () => { - expect(providerConfigFromInit({})).toEqual({ - timeout: DEFAULT_TIMEOUT, - maxRetries: DEFAULT_MAX_RETRIES, - }); - }); - - it("should pass through timeout and retries overrides", () => { - const timeout = 123456789; - const maxRetries = 987654321; - - expect(providerConfigFromInit({ timeout, maxRetries })).toEqual({ - timeout, - maxRetries, - }); - }); -}); diff --git a/packages/credential-provider-imds/src/remoteProvider/RemoteProviderInit.ts b/packages/credential-provider-imds/src/remoteProvider/RemoteProviderInit.ts deleted file mode 100644 index 2418972cf692..000000000000 --- a/packages/credential-provider-imds/src/remoteProvider/RemoteProviderInit.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Logger } from "@aws-sdk/types"; - -/** - * @internal - */ -export const DEFAULT_TIMEOUT = 1000; - -// The default in AWS SDK for Python and CLI (botocore) is no retry or one attempt -// https://github.com/boto/botocore/blob/646c61a7065933e75bab545b785e6098bc94c081/botocore/utils.py#L273 -/** - * @internal - */ -export const DEFAULT_MAX_RETRIES = 0; - -/** - * @internal - */ -export interface RemoteProviderConfig { - /** - * The connection timeout (in milliseconds) - */ - timeout: number; - - /** - * The maximum number of times the HTTP connection should be retried - */ - maxRetries: number; -} - -/** - * @internal - */ -export interface RemoteProviderInit extends Partial { - logger?: Logger; -} - -/** - * @internal - */ -export const providerConfigFromInit = ({ - maxRetries = DEFAULT_MAX_RETRIES, - timeout = DEFAULT_TIMEOUT, -}: RemoteProviderInit): RemoteProviderConfig => ({ maxRetries, timeout }); diff --git a/packages/credential-provider-imds/src/remoteProvider/httpRequest.spec.ts b/packages/credential-provider-imds/src/remoteProvider/httpRequest.spec.ts deleted file mode 100644 index e3ded25880c7..000000000000 --- a/packages/credential-provider-imds/src/remoteProvider/httpRequest.spec.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { ProviderError } from "@aws-sdk/property-provider"; -import http, { createServer } from "http"; -import nock from "nock"; - -import { httpRequest } from "./httpRequest"; - -describe("httpRequest", () => { - const requestSpy = jest.spyOn(http, "request"); - let port: number; - const hostname = "localhost"; - const path = "/"; - - const getOpenPort = async (candidatePort = 4321): Promise => { - try { - return new Promise((resolve, reject) => { - const server = createServer(); - server.on("error", () => reject()); - server.listen(candidatePort); - server.close(() => resolve(candidatePort)); - }); - } catch (e) { - return await getOpenPort(candidatePort + 1); - } - }; - - beforeAll(async () => { - port = await getOpenPort(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("returns response", () => { - it("defaults to method GET", async () => { - const expectedResponse = "expectedResponse"; - const scope = nock(`http://${hostname}:${port}`).get(path).reply(200, expectedResponse); - - const response = await httpRequest({ hostname, path, port }); - expect(response.toString()).toStrictEqual(expectedResponse); - expect(requestSpy.mock.results[0].value.socket).toHaveProperty("destroyed", true); - - scope.done(); - }); - - it("uses method passed in options", async () => { - const method = "POST"; - const expectedResponse = "expectedResponse"; - const scope = nock(`http://${hostname}:${port}`).post(path).reply(200, expectedResponse); - - const response = await httpRequest({ hostname, path, port, method }); - expect(response.toString()).toStrictEqual(expectedResponse); - expect(requestSpy.mock.results[0].value.socket).toHaveProperty("destroyed", true); - - scope.done(); - }); - - it("works with IPv6 hostname with encapsulated brackets", async () => { - const expectedResponse = "expectedResponse"; - const encapsulatedIPv6Hostname = "[::1]"; - const scope = nock(`http://${encapsulatedIPv6Hostname}:${port}`).get(path).reply(200, expectedResponse); - - const response = await httpRequest({ hostname: encapsulatedIPv6Hostname, path, port }); - expect(response.toString()).toStrictEqual(expectedResponse); - expect(requestSpy.mock.results[0].value.socket).toHaveProperty("destroyed", true); - - scope.done(); - }); - }); - - describe("throws error", () => { - const errorOnStatusCode = async (statusCode: number) => { - it(`statusCode: ${statusCode}`, async () => { - const scope = nock(`http://${hostname}:${port}`).get(path).reply(statusCode, "continue"); - - await expect(httpRequest({ hostname, path, port })).rejects.toStrictEqual( - Object.assign(new ProviderError("Error response received from instance metadata service"), { statusCode }) - ); - expect(requestSpy.mock.results[0].value.socket).toHaveProperty("destroyed", true); - - scope.done(); - }); - }; - - it("when request throws error", async () => { - const scope = nock(`http://${hostname}:${port}`).get(path).replyWithError("error"); - - await expect(httpRequest({ hostname, path, port })).rejects.toStrictEqual( - new ProviderError("Unable to connect to instance metadata service") - ); - expect(requestSpy.mock.results[0].value.socket).toHaveProperty("destroyed", true); - - scope.done(); - }); - - describe("when request returns with statusCode < 200", () => { - [100, 101, 103].forEach(errorOnStatusCode); - }); - - describe("when request returns with statusCode >= 300", () => { - [300, 400, 500].forEach(errorOnStatusCode); - }); - }); - - it("timeout", async () => { - const timeout = 1000; - const scope = nock(`http://${hostname}:${port}`) - .get(path) - .delay(timeout * 2) - .reply(200, "expectedResponse"); - - await expect(httpRequest({ hostname, path, port, timeout })).rejects.toStrictEqual( - new ProviderError("TimeoutError from instance metadata service") - ); - expect(requestSpy.mock.results[0].value.socket).toHaveProperty("destroyed", true); - - nock.abortPendingRequests(); - scope.done(); - }); -}); diff --git a/packages/credential-provider-imds/src/remoteProvider/httpRequest.ts b/packages/credential-provider-imds/src/remoteProvider/httpRequest.ts deleted file mode 100644 index bfde45098131..000000000000 --- a/packages/credential-provider-imds/src/remoteProvider/httpRequest.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ProviderError } from "@aws-sdk/property-provider"; -import { Buffer } from "buffer"; -import { IncomingMessage, request, RequestOptions } from "http"; - -/** - * @internal - */ -export function httpRequest(options: RequestOptions): Promise { - return new Promise((resolve, reject) => { - const req = request({ - method: "GET", - ...options, - // Node.js http module doesn't accept hostname with square brackets - // Refs: https://github.com/nodejs/node/issues/39738 - hostname: options.hostname?.replace(/^\[(.+)\]$/, "$1"), - }); - - req.on("error", (err) => { - reject(Object.assign(new ProviderError("Unable to connect to instance metadata service"), err)); - req.destroy(); - }); - - req.on("timeout", () => { - reject(new ProviderError("TimeoutError from instance metadata service")); - req.destroy(); - }); - - req.on("response", (res: IncomingMessage) => { - const { statusCode = 400 } = res; - if (statusCode < 200 || 300 <= statusCode) { - reject( - Object.assign(new ProviderError("Error response received from instance metadata service"), { statusCode }) - ); - req.destroy(); - } - - const chunks: Array = []; - res.on("data", (chunk) => { - chunks.push(chunk as Buffer); - }); - res.on("end", () => { - resolve(Buffer.concat(chunks)); - req.destroy(); - }); - }); - - req.end(); - }); -} diff --git a/packages/credential-provider-imds/src/remoteProvider/index.ts b/packages/credential-provider-imds/src/remoteProvider/index.ts deleted file mode 100644 index ed18a703202c..000000000000 --- a/packages/credential-provider-imds/src/remoteProvider/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @internal - */ -export * from "./ImdsCredentials"; -/** - * @internal - */ -export * from "./RemoteProviderInit"; diff --git a/packages/credential-provider-imds/src/remoteProvider/retry.spec.ts b/packages/credential-provider-imds/src/remoteProvider/retry.spec.ts deleted file mode 100644 index c1b2f7d8b1d5..000000000000 --- a/packages/credential-provider-imds/src/remoteProvider/retry.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { retry } from "./retry"; - -describe("retry", () => { - const successMsg = "Success"; - const errorMsg = "Expected failure"; - const retries = 10; - const retryable = jest.fn().mockRejectedValue(errorMsg); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("should retry a function the specified number of times", async () => { - await expect(retry(retryable, retries)).rejects.toStrictEqual(errorMsg); - expect(retryable).toHaveBeenCalledTimes(retries + 1); - }); - - it("should not retry if successful", async () => { - retryable.mockResolvedValueOnce(successMsg); - await expect(retry(retryable, retries)).resolves.toStrictEqual(successMsg); - expect(retryable).toHaveBeenCalledTimes(1); - }); - - it("should stop retrying after the first successful invocation", async () => { - const successfulInvocationIndex = 3; - for (let i = 1; i < successfulInvocationIndex; i++) { - retryable.mockRejectedValueOnce(errorMsg); - } - retryable.mockResolvedValueOnce(successMsg); - - await expect(retry(retryable, retries)).resolves.toStrictEqual(successMsg); - expect(retryable).toHaveBeenCalledTimes(successfulInvocationIndex); - }); -}); diff --git a/packages/credential-provider-imds/src/remoteProvider/retry.ts b/packages/credential-provider-imds/src/remoteProvider/retry.ts deleted file mode 100644 index 757a4554b94d..000000000000 --- a/packages/credential-provider-imds/src/remoteProvider/retry.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @internal - */ -export interface RetryableProvider { - (): Promise; -} - -/** - * @internal - */ -export const retry = (toRetry: RetryableProvider, maxRetries: number): Promise => { - let promise = toRetry(); - for (let i = 0; i < maxRetries; i++) { - promise = promise.catch(toRetry); - } - - return promise; -}; diff --git a/packages/credential-provider-imds/src/types.ts b/packages/credential-provider-imds/src/types.ts deleted file mode 100644 index 3bfb4aad97a3..000000000000 --- a/packages/credential-provider-imds/src/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { AwsCredentialIdentity } from "@aws-sdk/types"; - -/** - * @internal - */ -export interface InstanceMetadataCredentials extends AwsCredentialIdentity { - readonly originalExpiration?: Date; -} diff --git a/packages/credential-provider-imds/src/utils/getExtendedInstanceMetadataCredentials.spec.ts b/packages/credential-provider-imds/src/utils/getExtendedInstanceMetadataCredentials.spec.ts deleted file mode 100644 index 8b7d4beb667b..000000000000 --- a/packages/credential-provider-imds/src/utils/getExtendedInstanceMetadataCredentials.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Logger } from "@aws-sdk/types"; - -import { getExtendedInstanceMetadataCredentials } from "./getExtendedInstanceMetadataCredentials"; - -describe("getExtendedInstanceMetadataCredentials()", () => { - let nowMock: jest.SpyInstance; - const staticSecret = { - accessKeyId: "key", - secretAccessKey: "secret", - }; - const logger: Logger = { - warn: jest.fn(), - } as any; - - beforeEach(() => { - jest.spyOn(global.Math, "random"); - nowMock = jest.spyOn(Date, "now").mockReturnValueOnce(new Date("2022-02-22T00:00:00Z").getTime()); - }); - - afterEach(() => { - nowMock.mockRestore(); - }); - - it("should extend the expiration random time(5~10 mins) from now", () => { - const anyDate: Date = "any date" as unknown as Date; - (Math.random as jest.Mock).mockReturnValue(0.5); - expect(getExtendedInstanceMetadataCredentials({ ...staticSecret, expiration: anyDate }, logger)).toEqual({ - ...staticSecret, - originalExpiration: anyDate, - expiration: new Date("2022-02-22T00:07:30Z"), - }); - expect(Math.random).toBeCalledTimes(1); - }); - - it("should print warning message when extending the credentials", () => { - const anyDate: Date = "any date" as unknown as Date; - getExtendedInstanceMetadataCredentials({ ...staticSecret, expiration: anyDate }, logger); - expect(logger.warn).toBeCalledWith(expect.stringContaining("Attempting credential expiration extension")); - }); -}); diff --git a/packages/credential-provider-imds/src/utils/getExtendedInstanceMetadataCredentials.ts b/packages/credential-provider-imds/src/utils/getExtendedInstanceMetadataCredentials.ts deleted file mode 100644 index 15e4e9a2fd33..000000000000 --- a/packages/credential-provider-imds/src/utils/getExtendedInstanceMetadataCredentials.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Logger } from "@aws-sdk/types"; - -import { InstanceMetadataCredentials } from "../types"; - -const STATIC_STABILITY_REFRESH_INTERVAL_SECONDS = 5 * 60; -const STATIC_STABILITY_REFRESH_INTERVAL_JITTER_WINDOW_SECONDS = 5 * 60; -const STATIC_STABILITY_DOC_URL = "https://docs.aws.amazon.com/sdkref/latest/guide/feature-static-credentials.html"; - -/** - * @internal - */ -export const getExtendedInstanceMetadataCredentials = ( - credentials: InstanceMetadataCredentials, - logger: Logger -): InstanceMetadataCredentials => { - const refreshInterval = - STATIC_STABILITY_REFRESH_INTERVAL_SECONDS + - Math.floor(Math.random() * STATIC_STABILITY_REFRESH_INTERVAL_JITTER_WINDOW_SECONDS); - const newExpiration = new Date(Date.now() + refreshInterval * 1000); - logger.warn( - "Attempting credential expiration extension due to a credential service availability issue. A refresh of these " + - "credentials will be attempted after ${new Date(newExpiration)}.\nFor more information, please visit: " + - STATIC_STABILITY_DOC_URL - ); - const originalExpiration = credentials.originalExpiration ?? credentials.expiration; - return { - ...credentials, - ...(originalExpiration ? { originalExpiration } : {}), - expiration: newExpiration, - }; -}; diff --git a/packages/credential-provider-imds/src/utils/getInstanceMetadataEndpoint.spec.ts b/packages/credential-provider-imds/src/utils/getInstanceMetadataEndpoint.spec.ts deleted file mode 100644 index 38bd3837c17c..000000000000 --- a/packages/credential-provider-imds/src/utils/getInstanceMetadataEndpoint.spec.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { loadConfig } from "@aws-sdk/node-config-provider"; -import { parseUrl } from "@aws-sdk/url-parser"; - -import { Endpoint } from "../config/Endpoint"; -import { ENDPOINT_CONFIG_OPTIONS } from "../config/EndpointConfigOptions"; -import { EndpointMode } from "../config/EndpointMode"; -import { ENDPOINT_MODE_CONFIG_OPTIONS } from "../config/EndpointModeConfigOptions"; -import { getInstanceMetadataEndpoint } from "./getInstanceMetadataEndpoint"; - -jest.mock("@aws-sdk/node-config-provider"); -jest.mock("@aws-sdk/url-parser"); - -describe(getInstanceMetadataEndpoint.name, () => { - let mockURL: string; - const mockEndpoint = { protocol: "http:", hostname: "localhost", port: "80" }; - - beforeEach(() => { - (parseUrl as jest.Mock).mockReturnValue(mockEndpoint); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("when endpoint is defined", () => { - afterEach(() => { - expect(loadConfig).toHaveBeenCalledTimes(1); - expect(loadConfig).toHaveBeenCalledWith(ENDPOINT_CONFIG_OPTIONS); - expect(parseUrl).toHaveBeenCalledTimes(1); - expect(parseUrl).toHaveBeenCalledWith(mockURL); - }); - - it("throws error when endpoint is invalid", () => { - mockURL = "invalid_endpoint"; - const mockError = new Error(`Invalid endpoint: ${mockURL}`); - (loadConfig as jest.Mock).mockReturnValueOnce(() => Promise.resolve(mockURL)); - (parseUrl as jest.Mock).mockImplementation(() => { - throw mockError; - }); - return expect(getInstanceMetadataEndpoint()).rejects.toThrow(mockError); - }); - - describe("returns host when endpoint is valid", () => { - const mockProtocol = "http:"; - const mockHostname = "127.0.0.1"; - - it("with port", async () => { - const mockPort = 80; - mockURL = `${mockProtocol}://${mockHostname}:${mockPort}`; - (loadConfig as jest.Mock).mockReturnValueOnce(() => Promise.resolve(mockURL)); - expect(await getInstanceMetadataEndpoint()).toStrictEqual(mockEndpoint); - }); - - it("without port", async () => { - mockURL = `${mockProtocol}://${mockHostname}`; - (loadConfig as jest.Mock).mockReturnValueOnce(() => Promise.resolve(mockURL)); - expect(await getInstanceMetadataEndpoint()).toStrictEqual(mockEndpoint); - }); - }); - }); - - describe("when endpoint is not defined", () => { - beforeEach(() => { - (loadConfig as jest.Mock).mockReturnValueOnce(() => Promise.resolve(undefined)); - }); - - afterEach(() => { - expect(loadConfig).toHaveBeenCalledTimes(2); - expect(loadConfig).toHaveBeenNthCalledWith(1, ENDPOINT_CONFIG_OPTIONS); - expect(loadConfig).toHaveBeenNthCalledWith(2, ENDPOINT_MODE_CONFIG_OPTIONS); - }); - - it.each([ - [Endpoint.IPv4, EndpointMode.IPv4], - [Endpoint.IPv6, EndpointMode.IPv6], - ])("returns %s when endpointMode=%s", async (endpoint, endpointMode) => { - (loadConfig as jest.Mock).mockReturnValueOnce(() => Promise.resolve(endpointMode)); - expect(await getInstanceMetadataEndpoint()).toEqual(mockEndpoint); - expect(parseUrl).toHaveBeenCalledTimes(1); - expect(parseUrl).toHaveBeenCalledWith(endpoint); - }); - - it(`throws Error when endpointMode is unsupported`, () => { - const unsupportedEndpointMode = "unsupportedEndpointMode"; - (loadConfig as jest.Mock).mockReturnValueOnce(() => Promise.resolve(unsupportedEndpointMode)); - return expect(getInstanceMetadataEndpoint()).rejects.toThrowError( - `Unsupported endpoint mode: ${unsupportedEndpointMode}.` + ` Select from ${Object.values(EndpointMode)}` - ); - }); - - it(`rethrows Error when reading endpointMode throws error`, () => { - const error = new Error("error"); - (loadConfig as jest.Mock).mockReturnValueOnce(() => Promise.reject(error)); - return expect(getInstanceMetadataEndpoint()).rejects.toThrow(error); - }); - }); - - describe("when reading endpoint throws error", () => { - it("rethrows error", () => { - const error = new Error("error"); - (loadConfig as jest.Mock).mockReturnValueOnce(() => Promise.reject(error)); - expect(getInstanceMetadataEndpoint()).rejects.toThrow(error); - expect(loadConfig).toHaveBeenCalledTimes(1); - expect(loadConfig).toHaveBeenCalledWith(ENDPOINT_CONFIG_OPTIONS); - }); - }); -}); diff --git a/packages/credential-provider-imds/src/utils/getInstanceMetadataEndpoint.ts b/packages/credential-provider-imds/src/utils/getInstanceMetadataEndpoint.ts deleted file mode 100644 index c1dcecc0952a..000000000000 --- a/packages/credential-provider-imds/src/utils/getInstanceMetadataEndpoint.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { loadConfig } from "@aws-sdk/node-config-provider"; -import { Endpoint } from "@aws-sdk/types"; -import { parseUrl } from "@aws-sdk/url-parser"; - -import { Endpoint as InstanceMetadataEndpoint } from "../config/Endpoint"; -import { CONFIG_ENDPOINT_NAME, ENDPOINT_CONFIG_OPTIONS, ENV_ENDPOINT_NAME } from "../config/EndpointConfigOptions"; -import { EndpointMode } from "../config/EndpointMode"; -import { - CONFIG_ENDPOINT_MODE_NAME, - ENDPOINT_MODE_CONFIG_OPTIONS, - ENV_ENDPOINT_MODE_NAME, -} from "../config/EndpointModeConfigOptions"; - -/** - * Returns the host to use for instance metadata service call. - * - * The host is read from endpoint which can be set either in - * {@link ENV_ENDPOINT_NAME} environment variable or {@link CONFIG_ENDPOINT_NAME} - * configuration property. - * - * If endpoint is not set, then endpoint mode is read either from - * {@link ENV_ENDPOINT_MODE_NAME} environment variable or {@link CONFIG_ENDPOINT_MODE_NAME} - * configuration property. If endpoint mode is not set, then default endpoint mode - * {@link EndpointMode.IPv4} is used. - * - * If endpoint mode is set to {@link EndpointMode.IPv4}, then the host is {@link Endpoint.IPv4}. - * If endpoint mode is set to {@link EndpointMode.IPv6}, then the host is {@link Endpoint.IPv6}. - * - * @returns Host to use for instance metadata service call. - * - * @internal - */ -export const getInstanceMetadataEndpoint = async (): Promise => - parseUrl((await getFromEndpointConfig()) || (await getFromEndpointModeConfig())); - -const getFromEndpointConfig = async (): Promise => loadConfig(ENDPOINT_CONFIG_OPTIONS)(); - -const getFromEndpointModeConfig = async (): Promise => { - const endpointMode = await loadConfig(ENDPOINT_MODE_CONFIG_OPTIONS)(); - switch (endpointMode) { - case EndpointMode.IPv4: - return InstanceMetadataEndpoint.IPv4; - case EndpointMode.IPv6: - return InstanceMetadataEndpoint.IPv6; - default: - throw new Error(`Unsupported endpoint mode: ${endpointMode}.` + ` Select from ${Object.values(EndpointMode)}`); - } -}; diff --git a/packages/credential-provider-imds/src/utils/staticStabilityProvider.spec.ts b/packages/credential-provider-imds/src/utils/staticStabilityProvider.spec.ts deleted file mode 100644 index 5b6292b7cab1..000000000000 --- a/packages/credential-provider-imds/src/utils/staticStabilityProvider.spec.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Logger } from "@aws-sdk/types"; - -import { getExtendedInstanceMetadataCredentials } from "./getExtendedInstanceMetadataCredentials"; -import { staticStabilityProvider } from "./staticStabilityProvider"; - -jest.mock("./getExtendedInstanceMetadataCredentials"); - -describe("staticStabilityProvider", () => { - const ONE_HOUR_IN_FUTURE = new Date(Date.now() + 60 * 60 * 1000); - const mockCreds = { - accessKeyId: "key", - secretAccessKey: "secret", - sessionToken: "settion", - expiration: ONE_HOUR_IN_FUTURE, - }; - - beforeEach(() => { - (getExtendedInstanceMetadataCredentials as jest.Mock).mockImplementation( - (() => { - let extensionCount = 0; - return (input) => { - extensionCount++; - return { - ...input, - expiration: `Extending expiration count: ${extensionCount}`, - }; - }; - })() - ); - jest.spyOn(global.console, "warn").mockImplementation(() => {}); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it("should refresh credentials if provider is functional", async () => { - const provider = jest.fn(); - const stableProvider = staticStabilityProvider(provider); - const repeat = 3; - for (let i = 0; i < repeat; i++) { - const newCreds = { ...mockCreds, accessKeyId: String(i + 1) }; - provider.mockReset().mockResolvedValue(newCreds); - expect(await stableProvider()).toEqual(newCreds); - } - }); - - it("should throw if cannot load credentials at 1st load", async () => { - const provider = jest.fn().mockRejectedValue("Error"); - try { - await staticStabilityProvider(provider)(); - fail("This provider should throw"); - } catch (e) { - expect(getExtendedInstanceMetadataCredentials).not.toBeCalled(); - expect(provider).toBeCalledTimes(1); - expect(e).toEqual("Error"); - } - }); - - it("should extend expired credentials if refresh fails", async () => { - const provider = jest.fn().mockResolvedValueOnce(mockCreds).mockRejectedValue("Error"); - const stableProvider = staticStabilityProvider(provider); - expect(await stableProvider()).toEqual(mockCreds); - const repeat = 3; - for (let i = 0; i < repeat; i++) { - const newCreds = await stableProvider(); - expect(newCreds).toMatchObject({ ...mockCreds, expiration: expect.stringContaining(`count: ${i + 1}`) }); - expect(console.warn).toHaveBeenLastCalledWith( - expect.stringContaining("Credential renew failed:"), - expect.anything() - ); - } - expect(getExtendedInstanceMetadataCredentials).toBeCalledTimes(repeat); - expect(console.warn).toBeCalledTimes(repeat); - }); - - it("should extend expired credentials if loaded expired credentials", async () => { - const ONE_HOUR_AGO = new Date(Date.now() - 60 * 60 * 1000); - const provider = jest.fn().mockResolvedValue({ ...mockCreds, expiration: ONE_HOUR_AGO }); - const stableProvider = staticStabilityProvider(provider); - const repeat = 3; - for (let i = 0; i < repeat; i++) { - const newCreds = await stableProvider(); - expect(newCreds).toMatchObject({ ...mockCreds, expiration: expect.stringContaining(`count: ${i + 1}`) }); - } - expect(getExtendedInstanceMetadataCredentials).toBeCalledTimes(repeat); - expect(console.warn).not.toBeCalled(); - }); - - it("should allow custom logger to print warning messages", async () => { - const provider = jest.fn().mockResolvedValueOnce(mockCreds).mockRejectedValue("Error"); - const logger = { warn: jest.fn() } as unknown as Logger; - const stableProvider = staticStabilityProvider(provider, { logger }); - expect(await stableProvider()).toEqual(mockCreds); // load initial creds - await stableProvider(); - expect(logger.warn).toBeCalledTimes(1); - expect(console.warn).toBeCalledTimes(0); - }); -}); diff --git a/packages/credential-provider-imds/src/utils/staticStabilityProvider.ts b/packages/credential-provider-imds/src/utils/staticStabilityProvider.ts deleted file mode 100644 index 3edecd29617f..000000000000 --- a/packages/credential-provider-imds/src/utils/staticStabilityProvider.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { AwsCredentialIdentity, Logger, Provider } from "@aws-sdk/types"; - -import { InstanceMetadataCredentials } from "../types"; -import { getExtendedInstanceMetadataCredentials } from "./getExtendedInstanceMetadataCredentials"; - -/** - * @internal - * - * IMDS credential supports static stability feature. When used, the expiration - * of recently issued credentials is extended. The server side allows using - * the recently expired credentials. This mitigates impact when clients using - * refreshable credentials are unable to retrieve updates. - * - * @param provider Credential provider - * @returns A credential provider that supports static stability - */ -export const staticStabilityProvider = ( - provider: Provider, - options: { - logger?: Logger; - } = {} -): Provider => { - // Unlike normal SDK logger message, the key extension message must be transparent to users. - // When customer doesn't supply a custom logger, we need to log the warnings to console. - const logger = options?.logger || console; - let pastCredentials: InstanceMetadataCredentials; - return async () => { - let credentials: InstanceMetadataCredentials; - try { - credentials = await provider(); - if (credentials.expiration && credentials.expiration.getTime() < Date.now()) { - credentials = getExtendedInstanceMetadataCredentials(credentials, logger); - } - } catch (e) { - if (pastCredentials) { - logger.warn("Credential renew failed: ", e); - credentials = getExtendedInstanceMetadataCredentials(pastCredentials, logger); - } else { - throw e; - } - } - pastCredentials = credentials; - return credentials; - }; -}; diff --git a/packages/eventstream-codec/jest.config.js b/packages/eventstream-codec/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/eventstream-codec/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/eventstream-codec/package.json b/packages/eventstream-codec/package.json index b6245512f969..fd6679f5fa7a 100644 --- a/packages/eventstream-codec/package.json +++ b/packages/eventstream-codec/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest --coverage" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,9 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@aws-sdk/types": "*", - "@aws-sdk/util-hex-encoding": "*", + "@smithy/eventstream-codec": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/eventstream-codec/scripts/buildTestVectorsFixture.js b/packages/eventstream-codec/scripts/buildTestVectorsFixture.js deleted file mode 100644 index bc6b96fefb51..000000000000 --- a/packages/eventstream-codec/scripts/buildTestVectorsFixture.js +++ /dev/null @@ -1,80 +0,0 @@ -const { Buffer } = require("buffer"); -const { readdirSync, readFileSync, writeFileSync } = require("fs"); -const { dirname, join } = require("path"); - -const HEADER_TYPES = ["boolean", "byte", "short", "integer", "long", "binary", "string", "timestamp", "uuid"]; - -const vectorsDir = join(dirname(__dirname), "test_vectors"); -let vectors = "\n"; - -for (const dirName of ["positive", "negative"]) { - const encodedVectorsDir = join(vectorsDir, "encoded", dirName); - const decodedVectorsDir = join(vectorsDir, "decoded", dirName); - - for (const vectorName of readdirSync(encodedVectorsDir)) { - vectors += ` ${vectorName}: { - expectation: '${dirName === "positive" ? "success" : "failure"}', - encoded: Uint8Array.from([${readFileSync(join(encodedVectorsDir, vectorName)) - .map((byte) => byte.toString(10)) - .join(", ")}]), -`; - - if (dirName === "positive") { - const decoded = JSON.parse(readFileSync(join(decodedVectorsDir, vectorName))); - const headers = decoded.headers - .map( - (declaration) => - ` '${declaration.name}': { - type: '${HEADER_TYPES[declaration.type]}', - value: ${headerValue(declaration.type, declaration.value)}, - },` - ) - .join("\n"); - - vectors += ` decoded: { - headers: { -${headers} - }, - body: ${writeBuffer(Buffer.from(decoded.payload, "base64"))}, - }, -`; - } - - vectors += " },\n"; - } -} - -writeFileSync( - join(dirname(__dirname), "src", "TestVectors.fixture.ts"), - `import { TestVectors } from './vectorTypes.fixture'; -import { Int64 } from './Int64'; - -export const vectors: TestVectors = {${vectors}}; -` -); - -function headerValue(type, vectorRepresentation) { - switch (type) { - case 0: - return "true"; - case 1: - return "false"; - case 5: - return `Int64.fromNumber(${vectorRepresentation})`; - case 6: - return writeBuffer(Buffer.from(vectorRepresentation, "base64")); - case 7: - return `'${Buffer.from(vectorRepresentation, "base64").toString()}'`; - case 8: - return `new Date(${vectorRepresentation})`; - case 9: - const hex = Buffer.from(vectorRepresentation, "base64").toString("hex"); - return `'${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}'`; - default: - return vectorRepresentation; - } -} - -function writeBuffer(buffer) { - return `Uint8Array.from([${buffer.map((byte) => byte.toString(10)).join(", ")}])`; -} diff --git a/packages/eventstream-codec/src/EventStreamCodec.spec.ts b/packages/eventstream-codec/src/EventStreamCodec.spec.ts deleted file mode 100644 index 53825d5897bc..000000000000 --- a/packages/eventstream-codec/src/EventStreamCodec.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { fromUtf8, toUtf8 } from "@aws-sdk/util-utf8"; - -import { EventStreamCodec } from "./EventStreamCodec"; -import { vectors } from "./TestVectors.fixture"; - -describe("eventstream parsing", () => { - const eventStreamCodec = new EventStreamCodec(toUtf8, fromUtf8); - - for (const vectorName of Object.keys(vectors)) { - const vector = vectors[vectorName]; - it(`should handle the ${vectorName} test case`, () => { - if (vector.expectation === "failure") { - expect(() => eventStreamCodec.decode(vector.encoded)).toThrow(); - } else { - expect(eventStreamCodec.encode(vector.decoded)).toEqual(vector.encoded); - expect(eventStreamCodec.decode(vector.encoded)).toEqual(vector.decoded); - } - }); - } -}); diff --git a/packages/eventstream-codec/src/EventStreamCodec.ts b/packages/eventstream-codec/src/EventStreamCodec.ts deleted file mode 100644 index 8c04c0ff22c1..000000000000 --- a/packages/eventstream-codec/src/EventStreamCodec.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Crc32 } from "@aws-crypto/crc32"; -import { - AvailableMessage, - AvailableMessages, - Message, - MessageDecoder, - MessageEncoder, - MessageHeaders, -} from "@aws-sdk/types"; -import { Decoder, Encoder } from "@aws-sdk/types"; - -import { HeaderMarshaller } from "./HeaderMarshaller"; -import { splitMessage } from "./splitMessage"; - -/** - * A Codec that can convert binary-packed event stream messages into - * JavaScript objects and back again into their binary format. - */ -export class EventStreamCodec implements MessageEncoder, MessageDecoder { - private readonly headerMarshaller: HeaderMarshaller; - private messageBuffer: Message[]; - - private isEndOfStream: boolean; - - constructor(toUtf8: Encoder, fromUtf8: Decoder) { - this.headerMarshaller = new HeaderMarshaller(toUtf8, fromUtf8); - this.messageBuffer = []; - this.isEndOfStream = false; - } - - feed(message: ArrayBufferView): void { - this.messageBuffer.push(this.decode(message)); - } - - endOfStream(): void { - this.isEndOfStream = true; - } - - getMessage(): AvailableMessage { - const message = this.messageBuffer.pop(); - const isEndOfStream = this.isEndOfStream; - - return { - getMessage(): Message | undefined { - return message; - }, - isEndOfStream(): boolean { - return isEndOfStream; - }, - }; - } - - getAvailableMessages(): AvailableMessages { - const messages = this.messageBuffer; - this.messageBuffer = []; - const isEndOfStream = this.isEndOfStream; - - return { - getMessages(): Message[] { - return messages; - }, - isEndOfStream(): boolean { - return isEndOfStream; - }, - }; - } - - /** - * Convert a structured JavaScript object with tagged headers into a binary - * event stream message. - */ - encode({ headers: rawHeaders, body }: Message): Uint8Array { - const headers = this.headerMarshaller.format(rawHeaders); - const length = headers.byteLength + body.byteLength + 16; - - const out = new Uint8Array(length); - const view = new DataView(out.buffer, out.byteOffset, out.byteLength); - const checksum = new Crc32(); - - // Format message - view.setUint32(0, length, false); - view.setUint32(4, headers.byteLength, false); - view.setUint32(8, checksum.update(out.subarray(0, 8)).digest(), false); - out.set(headers, 12); - out.set(body, headers.byteLength + 12); - - // Write trailing message checksum - view.setUint32(length - 4, checksum.update(out.subarray(8, length - 4)).digest(), false); - - return out; - } - - /** - * Convert a binary event stream message into a JavaScript object with an - * opaque, binary body and tagged, parsed headers. - */ - decode(message: ArrayBufferView): Message { - const { headers, body } = splitMessage(message); - - return { headers: this.headerMarshaller.parse(headers), body }; - } - - /** - * Convert a structured JavaScript object with tagged headers into a binary - * event stream message header. - */ - formatHeaders(rawHeaders: MessageHeaders): Uint8Array { - return this.headerMarshaller.format(rawHeaders); - } -} diff --git a/packages/eventstream-codec/src/HeaderMarshaller.spec.ts b/packages/eventstream-codec/src/HeaderMarshaller.spec.ts deleted file mode 100644 index 570d40830051..000000000000 --- a/packages/eventstream-codec/src/HeaderMarshaller.spec.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { MessageHeaders } from "@aws-sdk/types"; -import { fromUtf8, toUtf8 } from "@aws-sdk/util-utf8"; - -import { HeaderMarshaller } from "./HeaderMarshaller"; -import { Int64 } from "./Int64"; - -describe("HeaderMarshaller", () => { - const marshaller = new HeaderMarshaller(toUtf8, fromUtf8); - const name = Uint8Array.from([0x04, 0xf0, 0x9f, 0xa6, 0x84]); - - const testCases: Array<[string, Uint8Array, MessageHeaders]> = [ - [ - "boolean true headers", - Uint8Array.from([...name, 0]), - { - "🦄": { - type: "boolean", - value: true, - }, - }, - ], - [ - "boolean false headers", - Uint8Array.from([...name, 1]), - { - "🦄": { - type: "boolean", - value: false, - }, - }, - ], - [ - "byte headers", - Uint8Array.from([...name, 2, 0x7f]), - { - "🦄": { - type: "byte", - value: 127, - }, - }, - ], - [ - "short headers", - Uint8Array.from([...name, 3, 0x7f, 0xff]), - { - "🦄": { - type: "short", - value: 32767, - }, - }, - ], - [ - "integer headers", - Uint8Array.from([...name, 4, 0x7f, 0xff, 0xff, 0xff]), - { - "🦄": { - type: "integer", - value: 2147483647, - }, - }, - ], - [ - "long headers", - Uint8Array.from([...name, 5, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), - { - "🦄": { - type: "long", - value: new Int64(Uint8Array.from([0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])), - }, - }, - ], - [ - "binary headers", - Uint8Array.from([...name, 6, 0x00, 0x08, 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]), - { - "🦄": { - type: "binary", - value: Uint8Array.from([0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]), - }, - }, - ], - [ - "string headers", - Uint8Array.from([ - ...name, - 7, - 0x00, - 0x2e, - 0xd8, - 0xaf, - 0xd8, - 0xb3, - 0xd8, - 0xaa, - 0xe2, - 0x80, - 0x8c, - 0xd9, - 0x86, - 0xd9, - 0x88, - 0xd8, - 0xb4, - 0xd8, - 0xaa, - 0xd9, - 0x87, - 0xe2, - 0x80, - 0x8c, - 0xd9, - 0x87, - 0xd8, - 0xa7, - 0x20, - 0xd9, - 0x86, - 0xd9, - 0x85, - 0xdb, - 0x8c, - 0xe2, - 0x80, - 0x8c, - 0xd8, - 0xb3, - 0xd9, - 0x88, - 0xd8, - 0xb2, - 0xd9, - 0x86, - 0xd8, - 0xaf, - ]), - { - "🦄": { - type: "string", - value: "دست‌نوشته‌ها نمی‌سوزند", - }, - }, - ], - [ - "timestamp headers", - Uint8Array.from([...name, 8, 0x00, 0x00, 0x01, 0x61, 0x97, 0x16, 0xac, 0xc2]), - { - "🦄": { - type: "timestamp", - value: new Date(1518658301122), - }, - }, - ], - [ - "UUID headers", - Uint8Array.from([ - ...name, - 9, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - ]), - { - "🦄": { - type: "uuid", - value: "ffffffff-ffff-ffff-ffff-ffffffffffff", - }, - }, - ], - [ - "a sequence of headers", - Uint8Array.from([ - 0x04, 0xf0, 0x9f, 0xa6, 0x84, 0x06, 0x00, 0x04, 0xde, 0xad, 0xbe, 0xef, 0x04, 0xf0, 0x9f, 0x8f, 0x87, 0x00, - 0x04, 0xf0, 0x9f, 0x90, 0x8e, 0x07, 0x00, 0x07, 0xe2, 0x98, 0x83, 0xf0, 0x9f, 0x92, 0xa9, 0x04, 0xf0, 0x9f, - 0x90, 0xb4, 0x01, - ]), - { - "🦄": { - type: "binary", - value: Uint8Array.from([0xde, 0xad, 0xbe, 0xef]), - }, - "🏇": { - type: "boolean", - value: true, - }, - "🐎": { - type: "string", - value: "☃💩", - }, - "🐴": { - type: "boolean", - value: false, - }, - }, - ], - ]; - - describe("#format", () => { - for (const [description, encoded, decoded] of testCases) { - it(`should format ${description}`, () => { - expect(marshaller.format(decoded)).toEqual(encoded); - }); - } - - it("should throw if it receives an invalid UUID", () => { - expect(() => - marshaller.format({ - uuid: { - type: "uuid", - value: "foo", - }, - }) - ).toThrowError("Invalid UUID received"); - }); - }); - - describe("#parse", () => { - for (const [description, encoded, decoded] of testCases) { - it(`should parse ${description}`, () => { - expect(marshaller.parse(new DataView(encoded.buffer))).toEqual(decoded); - }); - } - - it("should throw when unrecognized header types are encountered", () => { - const header = Uint8Array.from([...name, 10]); - - expect(() => marshaller.parse(new DataView(header.buffer))).toThrowError("Unrecognized header type tag"); - }); - }); -}); diff --git a/packages/eventstream-codec/src/HeaderMarshaller.ts b/packages/eventstream-codec/src/HeaderMarshaller.ts deleted file mode 100644 index ce112a1cee90..000000000000 --- a/packages/eventstream-codec/src/HeaderMarshaller.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { Decoder, Encoder, MessageHeaders, MessageHeaderValue } from "@aws-sdk/types"; -import { fromHex, toHex } from "@aws-sdk/util-hex-encoding"; - -import { Int64 } from "./Int64"; - -/** - * @internal - */ -export class HeaderMarshaller { - constructor(private readonly toUtf8: Encoder, private readonly fromUtf8: Decoder) {} - - format(headers: MessageHeaders): Uint8Array { - const chunks: Array = []; - - for (const headerName of Object.keys(headers)) { - const bytes = this.fromUtf8(headerName); - chunks.push(Uint8Array.from([bytes.byteLength]), bytes, this.formatHeaderValue(headers[headerName])); - } - - const out = new Uint8Array(chunks.reduce((carry, bytes) => carry + bytes.byteLength, 0)); - let position = 0; - for (const chunk of chunks) { - out.set(chunk, position); - position += chunk.byteLength; - } - - return out; - } - - private formatHeaderValue(header: MessageHeaderValue): Uint8Array { - switch (header.type) { - case "boolean": - return Uint8Array.from([header.value ? HEADER_VALUE_TYPE.boolTrue : HEADER_VALUE_TYPE.boolFalse]); - case "byte": - return Uint8Array.from([HEADER_VALUE_TYPE.byte, header.value]); - case "short": - const shortView = new DataView(new ArrayBuffer(3)); - shortView.setUint8(0, HEADER_VALUE_TYPE.short); - shortView.setInt16(1, header.value, false); - return new Uint8Array(shortView.buffer); - case "integer": - const intView = new DataView(new ArrayBuffer(5)); - intView.setUint8(0, HEADER_VALUE_TYPE.integer); - intView.setInt32(1, header.value, false); - return new Uint8Array(intView.buffer); - case "long": - const longBytes = new Uint8Array(9); - longBytes[0] = HEADER_VALUE_TYPE.long; - longBytes.set(header.value.bytes, 1); - return longBytes; - case "binary": - const binView = new DataView(new ArrayBuffer(3 + header.value.byteLength)); - binView.setUint8(0, HEADER_VALUE_TYPE.byteArray); - binView.setUint16(1, header.value.byteLength, false); - const binBytes = new Uint8Array(binView.buffer); - binBytes.set(header.value, 3); - return binBytes; - case "string": - const utf8Bytes = this.fromUtf8(header.value); - const strView = new DataView(new ArrayBuffer(3 + utf8Bytes.byteLength)); - strView.setUint8(0, HEADER_VALUE_TYPE.string); - strView.setUint16(1, utf8Bytes.byteLength, false); - const strBytes = new Uint8Array(strView.buffer); - strBytes.set(utf8Bytes, 3); - return strBytes; - case "timestamp": - const tsBytes = new Uint8Array(9); - tsBytes[0] = HEADER_VALUE_TYPE.timestamp; - tsBytes.set(Int64.fromNumber(header.value.valueOf()).bytes, 1); - return tsBytes; - case "uuid": - if (!UUID_PATTERN.test(header.value)) { - throw new Error(`Invalid UUID received: ${header.value}`); - } - - const uuidBytes = new Uint8Array(17); - uuidBytes[0] = HEADER_VALUE_TYPE.uuid; - uuidBytes.set(fromHex(header.value.replace(/\-/g, "")), 1); - return uuidBytes; - } - } - - parse(headers: DataView): MessageHeaders { - const out: MessageHeaders = {}; - let position = 0; - - while (position < headers.byteLength) { - const nameLength = headers.getUint8(position++); - const name = this.toUtf8(new Uint8Array(headers.buffer, headers.byteOffset + position, nameLength)); - position += nameLength; - - switch (headers.getUint8(position++)) { - case HEADER_VALUE_TYPE.boolTrue: - out[name] = { - type: BOOLEAN_TAG, - value: true, - }; - break; - case HEADER_VALUE_TYPE.boolFalse: - out[name] = { - type: BOOLEAN_TAG, - value: false, - }; - break; - case HEADER_VALUE_TYPE.byte: - out[name] = { - type: BYTE_TAG, - value: headers.getInt8(position++), - }; - break; - case HEADER_VALUE_TYPE.short: - out[name] = { - type: SHORT_TAG, - value: headers.getInt16(position, false), - }; - position += 2; - break; - case HEADER_VALUE_TYPE.integer: - out[name] = { - type: INT_TAG, - value: headers.getInt32(position, false), - }; - position += 4; - break; - case HEADER_VALUE_TYPE.long: - out[name] = { - type: LONG_TAG, - value: new Int64(new Uint8Array(headers.buffer, headers.byteOffset + position, 8)), - }; - position += 8; - break; - case HEADER_VALUE_TYPE.byteArray: - const binaryLength = headers.getUint16(position, false); - position += 2; - out[name] = { - type: BINARY_TAG, - value: new Uint8Array(headers.buffer, headers.byteOffset + position, binaryLength), - }; - position += binaryLength; - break; - case HEADER_VALUE_TYPE.string: - const stringLength = headers.getUint16(position, false); - position += 2; - out[name] = { - type: STRING_TAG, - value: this.toUtf8(new Uint8Array(headers.buffer, headers.byteOffset + position, stringLength)), - }; - position += stringLength; - break; - case HEADER_VALUE_TYPE.timestamp: - out[name] = { - type: TIMESTAMP_TAG, - value: new Date(new Int64(new Uint8Array(headers.buffer, headers.byteOffset + position, 8)).valueOf()), - }; - position += 8; - break; - case HEADER_VALUE_TYPE.uuid: - const uuidBytes = new Uint8Array(headers.buffer, headers.byteOffset + position, 16); - position += 16; - out[name] = { - type: UUID_TAG, - value: `${toHex(uuidBytes.subarray(0, 4))}-${toHex(uuidBytes.subarray(4, 6))}-${toHex( - uuidBytes.subarray(6, 8) - )}-${toHex(uuidBytes.subarray(8, 10))}-${toHex(uuidBytes.subarray(10))}`, - }; - break; - default: - throw new Error(`Unrecognized header type tag`); - } - } - - return out; - } -} - -const enum HEADER_VALUE_TYPE { - boolTrue = 0, - boolFalse, - byte, - short, - integer, - long, - byteArray, - string, - timestamp, - uuid, -} - -const BOOLEAN_TAG = "boolean"; -const BYTE_TAG = "byte"; -const SHORT_TAG = "short"; -const INT_TAG = "integer"; -const LONG_TAG = "long"; -const BINARY_TAG = "binary"; -const STRING_TAG = "string"; -const TIMESTAMP_TAG = "timestamp"; -const UUID_TAG = "uuid"; - -const UUID_PATTERN = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/; diff --git a/packages/eventstream-codec/src/Int64.spec.ts b/packages/eventstream-codec/src/Int64.spec.ts deleted file mode 100644 index 48363d6c43da..000000000000 --- a/packages/eventstream-codec/src/Int64.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Int64 } from "./Int64"; - -describe("Int64", () => { - it("should hold integers greater than Number.MAX_SAFE_INTEGER without losing precision", () => { - const bytes = Uint8Array.from([0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); - - expect(new Int64(bytes).bytes).toEqual(bytes); - }); - - it("should allow the use of Int64s in arithmetic expressions", () => { - const bytes = Uint8Array.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10]); - - expect((new Int64(bytes) as any) + 4).toBe(20); - }); - - it("should allow the use of negative Int64s in arithmetic expressions", () => { - const bytes = Uint8Array.from([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); - - expect((new Int64(bytes) as any) + 2 ** 52).toBe(0); - }); - - it("should stringify negative Int64s in base 10", () => { - const bytes = Uint8Array.from([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe2]); - - expect(String(new Int64(bytes))).toBe("-30"); - }); - - it("should throw when given a buffer of the wrong byte length", () => { - expect(() => new Int64(new Uint8Array(0))).toThrow(); - }); - - it("should convert numbers into Int64 values", () => { - expect(Int64.fromNumber(2147483647).bytes).toEqual( - Uint8Array.from([0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff]) - ); - }); - - it("should convert negative numbers into Int64 values", () => { - expect(Int64.fromNumber(-2147483647).bytes).toEqual( - Uint8Array.from([0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x01]) - ); - }); - - it("should throw when a number larger than 2^63 -1 is provided", () => { - // eslint-disable-next-line @typescript-eslint/no-loss-of-precision - expect(() => Int64.fromNumber(9_323_372_036_854_775_807)).toThrow(); - }); - - it("should throw when a number smaller than -1 * 2^63 is provided", () => { - // eslint-disable-next-line @typescript-eslint/no-loss-of-precision - expect(() => Int64.fromNumber(-9_323_372_036_854_775_807)).toThrow(); - }); -}); diff --git a/packages/eventstream-codec/src/Int64.ts b/packages/eventstream-codec/src/Int64.ts deleted file mode 100644 index ef7ab49dced3..000000000000 --- a/packages/eventstream-codec/src/Int64.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Int64 as IInt64 } from "@aws-sdk/types"; -import { toHex } from "@aws-sdk/util-hex-encoding"; - -export interface Int64 extends IInt64 {} - -/** - * A lossless representation of a signed, 64-bit integer. Instances of this - * class may be used in arithmetic expressions as if they were numeric - * primitives, but the binary representation will be preserved unchanged as the - * `bytes` property of the object. The bytes should be encoded as big-endian, - * two's complement integers. - */ -export class Int64 { - constructor(readonly bytes: Uint8Array) { - if (bytes.byteLength !== 8) { - throw new Error("Int64 buffers must be exactly 8 bytes"); - } - } - - static fromNumber(number: number): Int64 { - // eslint-disable-next-line @typescript-eslint/no-loss-of-precision - if (number > 9_223_372_036_854_775_807 || number < -9_223_372_036_854_775_808) { - throw new Error(`${number} is too large (or, if negative, too small) to represent as an Int64`); - } - - const bytes = new Uint8Array(8); - for (let i = 7, remaining = Math.abs(Math.round(number)); i > -1 && remaining > 0; i--, remaining /= 256) { - bytes[i] = remaining; - } - - if (number < 0) { - negate(bytes); - } - - return new Int64(bytes); - } - - /** - * Called implicitly by infix arithmetic operators. - */ - valueOf(): number { - const bytes = this.bytes.slice(0); - const negative = bytes[0] & 0b10000000; - if (negative) { - negate(bytes); - } - - return parseInt(toHex(bytes), 16) * (negative ? -1 : 1); - } - - toString() { - return String(this.valueOf()); - } -} - -function negate(bytes: Uint8Array): void { - for (let i = 0; i < 8; i++) { - bytes[i] ^= 0xff; - } - - for (let i = 7; i > -1; i--) { - bytes[i]++; - if (bytes[i] !== 0) break; - } -} diff --git a/packages/eventstream-codec/src/Message.ts b/packages/eventstream-codec/src/Message.ts deleted file mode 100644 index f14596dfebe6..000000000000 --- a/packages/eventstream-codec/src/Message.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Int64 } from "./Int64"; - -/** - * An event stream message. The headers and body properties will always be - * defined, with empty headers represented as an object with no keys and an - * empty body represented as a zero-length Uint8Array. - */ -export interface Message { - headers: MessageHeaders; - body: Uint8Array; -} - -export type MessageHeaders = Record; - -type HeaderValue = { type: K; value: V }; - -export type BooleanHeaderValue = HeaderValue<"boolean", boolean>; -export type ByteHeaderValue = HeaderValue<"byte", number>; -export type ShortHeaderValue = HeaderValue<"short", number>; -export type IntegerHeaderValue = HeaderValue<"integer", number>; -export type LongHeaderValue = HeaderValue<"long", Int64>; -export type BinaryHeaderValue = HeaderValue<"binary", Uint8Array>; -export type StringHeaderValue = HeaderValue<"string", string>; -export type TimestampHeaderValue = HeaderValue<"timestamp", Date>; -export type UuidHeaderValue = HeaderValue<"uuid", string>; - -export type MessageHeaderValue = - | BooleanHeaderValue - | ByteHeaderValue - | ShortHeaderValue - | IntegerHeaderValue - | LongHeaderValue - | BinaryHeaderValue - | StringHeaderValue - | TimestampHeaderValue - | UuidHeaderValue; diff --git a/packages/eventstream-codec/src/MessageDecoderStream.spec.ts b/packages/eventstream-codec/src/MessageDecoderStream.spec.ts deleted file mode 100644 index 4e3878fee4d7..000000000000 --- a/packages/eventstream-codec/src/MessageDecoderStream.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Message } from "@aws-sdk/types"; - -import { MessageDecoderStream } from "./MessageDecoderStream"; - -describe("MessageDecoderStream", () => { - it("returns decoded messages", async () => { - const message1 = { - headers: {}, - body: new Uint8Array(1), - }; - - const message2 = { - headers: {}, - body: new Uint8Array(2), - }; - - const messageDecoderMock = { - decode: jest.fn().mockReturnValueOnce(message1).mockReturnValueOnce(message2), - feed: jest.fn(), - endOfStream: jest.fn(), - getMessage: jest.fn(), - getAvailableMessages: jest.fn(), - }; - - const inputStream = async function* () { - yield new Uint8Array(0); - yield new Uint8Array(1); - }; - - const messageDecoderStream = new MessageDecoderStream({ - decoder: messageDecoderMock, - inputStream: inputStream(), - }); - - const messages: Array = []; - for await (const message of messageDecoderStream) { - messages.push(message); - } - expect(messages.length).toEqual(2); - expect(messages[0]).toEqual(message1); - expect(messages[1]).toEqual(message2); - }); -}); diff --git a/packages/eventstream-codec/src/MessageDecoderStream.ts b/packages/eventstream-codec/src/MessageDecoderStream.ts deleted file mode 100644 index 6257dbee4154..000000000000 --- a/packages/eventstream-codec/src/MessageDecoderStream.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Message, MessageDecoder } from "@aws-sdk/types"; - -/** - * @internal - */ -export interface MessageDecoderStreamOptions { - inputStream: AsyncIterable; - decoder: MessageDecoder; -} - -/** - * @internal - */ -export class MessageDecoderStream implements AsyncIterable { - constructor(private readonly options: MessageDecoderStreamOptions) {} - - [Symbol.asyncIterator](): AsyncIterator { - return this.asyncIterator(); - } - - private async *asyncIterator() { - for await (const bytes of this.options.inputStream) { - const decoded = this.options.decoder.decode(bytes); - yield decoded; - } - } -} diff --git a/packages/eventstream-codec/src/MessageEncoderStream.spec.ts b/packages/eventstream-codec/src/MessageEncoderStream.spec.ts deleted file mode 100644 index b8b1a4d583b1..000000000000 --- a/packages/eventstream-codec/src/MessageEncoderStream.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { MessageEncoderStream } from "./MessageEncoderStream"; - -describe("MessageEncoderStream", () => { - it("returns encoded stream with end frame", async () => { - const message1 = { - headers: {}, - body: new Uint8Array(1), - }; - - const message2 = { - headers: {}, - body: new Uint8Array(2), - }; - - const messageEncoderMock = { - encode: jest.fn().mockReturnValueOnce(new Uint8Array(1)).mockReturnValueOnce(new Uint8Array(2)), - }; - - const inputStream = async function* () { - yield message1; - yield message2; - }; - - const messageEncoderStream = new MessageEncoderStream({ - encoder: messageEncoderMock, - messageStream: inputStream(), - includeEndFrame: true, - }); - - const messages: Array = []; - for await (const encoded of messageEncoderStream) { - messages.push(encoded); - } - expect(messages.length).toEqual(3); - expect(messages[0]).toEqual(new Uint8Array(1)); - expect(messages[1]).toEqual(new Uint8Array(2)); - expect(messages[2]).toEqual(new Uint8Array(0)); - }); - - it("returns encoded stream without end frame", async () => { - const message1 = { - headers: {}, - body: new Uint8Array(1), - }; - - const message2 = { - headers: {}, - body: new Uint8Array(2), - }; - - const messageEncoderMock = { - encode: jest.fn().mockReturnValueOnce(new Uint8Array(1)).mockReturnValueOnce(new Uint8Array(2)), - }; - - const inputStream = async function* () { - yield message1; - yield message2; - }; - - const messageEncoderStream = new MessageEncoderStream({ - encoder: messageEncoderMock, - messageStream: inputStream(), - }); - - const messages: Array = []; - for await (const encoded of messageEncoderStream) { - messages.push(encoded); - } - expect(messages.length).toEqual(2); - expect(messages[0]).toEqual(new Uint8Array(1)); - expect(messages[1]).toEqual(new Uint8Array(2)); - }); -}); diff --git a/packages/eventstream-codec/src/MessageEncoderStream.ts b/packages/eventstream-codec/src/MessageEncoderStream.ts deleted file mode 100644 index 0531c9f3b318..000000000000 --- a/packages/eventstream-codec/src/MessageEncoderStream.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Message, MessageEncoder } from "@aws-sdk/types"; - -/** - * @internal - */ -export interface MessageEncoderStreamOptions { - messageStream: AsyncIterable; - encoder: MessageEncoder; - includeEndFrame?: Boolean; -} - -/** - * @internal - */ -export class MessageEncoderStream implements AsyncIterable { - constructor(private readonly options: MessageEncoderStreamOptions) {} - - [Symbol.asyncIterator](): AsyncIterator { - return this.asyncIterator(); - } - - private async *asyncIterator() { - for await (const msg of this.options.messageStream) { - const encoded = this.options.encoder.encode(msg); - yield encoded; - } - if (this.options.includeEndFrame) { - yield new Uint8Array(0); - } - } -} diff --git a/packages/eventstream-codec/src/SmithyMessageDecoderStream.spec.ts b/packages/eventstream-codec/src/SmithyMessageDecoderStream.spec.ts deleted file mode 100644 index 41be8bac2726..000000000000 --- a/packages/eventstream-codec/src/SmithyMessageDecoderStream.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { SmithyMessageDecoderStream } from "./SmithyMessageDecoderStream"; - -describe("SmithyMessageDecoderStream", () => { - it("returns decoded stream", async () => { - const message1 = { - headers: {}, - body: new Uint8Array(1), - }; - - const message2 = { - headers: {}, - body: new Uint8Array(2), - }; - - const deserializer = jest - .fn() - .mockReturnValueOnce(Promise.resolve("first")) - .mockReturnValueOnce(Promise.resolve("second")); - - const inputStream = async function* () { - yield message1; - yield message2; - }; - - const stream = new SmithyMessageDecoderStream({ - messageStream: inputStream(), - deserializer: deserializer, - }); - - const messages: Array = []; - for await (const str of stream) { - messages.push(str); - } - expect(messages.length).toEqual(2); - expect(messages[0]).toEqual("first"); - expect(messages[1]).toEqual("second"); - }); -}); diff --git a/packages/eventstream-codec/src/SmithyMessageDecoderStream.ts b/packages/eventstream-codec/src/SmithyMessageDecoderStream.ts deleted file mode 100644 index ae7970b704ae..000000000000 --- a/packages/eventstream-codec/src/SmithyMessageDecoderStream.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Message } from "@aws-sdk/types"; - -/** - * @internal - */ -export interface SmithyMessageDecoderStreamOptions { - readonly messageStream: AsyncIterable; - readonly deserializer: (input: Message) => Promise; -} - -/** - * @internal - */ -export class SmithyMessageDecoderStream implements AsyncIterable { - constructor(private readonly options: SmithyMessageDecoderStreamOptions) {} - - [Symbol.asyncIterator](): AsyncIterator { - return this.asyncIterator(); - } - - private async *asyncIterator() { - for await (const message of this.options.messageStream) { - const deserialized = await this.options.deserializer(message); - if (deserialized === undefined) continue; - yield deserialized; - } - } -} diff --git a/packages/eventstream-codec/src/SmithyMessageEncoderStream.spec.ts b/packages/eventstream-codec/src/SmithyMessageEncoderStream.spec.ts deleted file mode 100644 index b54d62e3567f..000000000000 --- a/packages/eventstream-codec/src/SmithyMessageEncoderStream.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Message } from "@aws-sdk/types"; - -import { SmithyMessageEncoderStream } from "./SmithyMessageEncoderStream"; - -describe("SmithyMessageEncoderStream", () => { - it("returns encoded stream", async () => { - const message1 = { - headers: {}, - body: new Uint8Array(1), - }; - - const message2 = { - headers: {}, - body: new Uint8Array(2), - }; - - const serializer = jest.fn().mockReturnValueOnce(message1).mockReturnValueOnce(message2); - - const inputStream = async function* () { - yield "first"; - yield "second"; - }; - - const stream = new SmithyMessageEncoderStream({ - inputStream: inputStream(), - serializer: serializer, - }); - - const messages: Array = []; - for await (const str of stream) { - messages.push(str); - } - expect(messages.length).toEqual(2); - expect(messages[0]).toEqual(message1); - expect(messages[1]).toEqual(message2); - }); -}); diff --git a/packages/eventstream-codec/src/SmithyMessageEncoderStream.ts b/packages/eventstream-codec/src/SmithyMessageEncoderStream.ts deleted file mode 100644 index b489da126e1b..000000000000 --- a/packages/eventstream-codec/src/SmithyMessageEncoderStream.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Message } from "@aws-sdk/types"; - -/** - * @internal - */ -export interface SmithyMessageEncoderStreamOptions { - inputStream: AsyncIterable; - serializer: (event: T) => Message; -} - -/** - * @internal - */ -export class SmithyMessageEncoderStream implements AsyncIterable { - constructor(private readonly options: SmithyMessageEncoderStreamOptions) {} - - [Symbol.asyncIterator](): AsyncIterator { - return this.asyncIterator(); - } - - private async *asyncIterator() { - for await (const chunk of this.options.inputStream) { - const payloadBuf = this.options.serializer(chunk); - yield payloadBuf; - } - } -} diff --git a/packages/eventstream-codec/src/TestVectors.fixture.ts b/packages/eventstream-codec/src/TestVectors.fixture.ts deleted file mode 100644 index c83ca5c37ad2..000000000000 --- a/packages/eventstream-codec/src/TestVectors.fixture.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { Int64 } from "./Int64"; -import { TestVectors } from "./vectorTypes.fixture"; - -export const vectors: TestVectors = { - all_headers: { - expectation: "success", - encoded: Uint8Array.from([ - 0, 0, 0, 204, 0, 0, 0, 175, 15, 174, 100, 202, 10, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 4, 0, 0, 160, - 12, 12, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 16, 97, 112, 112, 108, 105, 99, 97, 116, - 105, 111, 110, 47, 106, 115, 111, 110, 10, 98, 111, 111, 108, 32, 102, 97, 108, 115, 101, 1, 9, 98, 111, 111, 108, - 32, 116, 114, 117, 101, 0, 4, 98, 121, 116, 101, 2, 207, 8, 98, 121, 116, 101, 32, 98, 117, 102, 6, 0, 20, 73, 39, - 109, 32, 97, 32, 108, 105, 116, 116, 108, 101, 32, 116, 101, 97, 112, 111, 116, 33, 9, 116, 105, 109, 101, 115, - 116, 97, 109, 112, 8, 0, 0, 0, 0, 0, 132, 95, 237, 5, 105, 110, 116, 49, 54, 3, 0, 42, 5, 105, 110, 116, 54, 52, - 5, 0, 0, 0, 0, 2, 135, 87, 178, 4, 117, 117, 105, 100, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 123, 39, 102, 111, 111, 39, 58, 39, 98, 97, 114, 39, 125, 171, 165, 241, 12, - ]), - decoded: { - headers: { - "event-type": { - type: "integer", - value: 40972, - }, - "content-type": { - type: "string", - value: "application/json", - }, - "bool false": { - type: "boolean", - value: false, - }, - "bool true": { - type: "boolean", - value: true, - }, - byte: { - type: "byte", - value: -49, - }, - "byte buf": { - type: "binary", - value: Uint8Array.from([ - 73, 39, 109, 32, 97, 32, 108, 105, 116, 116, 108, 101, 32, 116, 101, 97, 112, 111, 116, 33, - ]), - }, - timestamp: { - type: "timestamp", - value: new Date(8675309), - }, - int16: { - type: "short", - value: 42, - }, - int64: { - type: "long", - value: Int64.fromNumber(42424242), - }, - uuid: { - type: "uuid", - value: "01020304-0506-0708-090a-0b0c0d0e0f10", - }, - }, - body: Uint8Array.from([123, 39, 102, 111, 111, 39, 58, 39, 98, 97, 114, 39, 125]), - }, - }, - empty_message: { - expectation: "success", - encoded: Uint8Array.from([0, 0, 0, 16, 0, 0, 0, 0, 5, 194, 72, 235, 125, 152, 200, 255]), - decoded: { - headers: {}, - body: Uint8Array.from([]), - }, - }, - int32_header: { - expectation: "success", - encoded: Uint8Array.from([ - 0, 0, 0, 45, 0, 0, 0, 16, 65, 196, 36, 184, 10, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 4, 0, 0, 160, 12, - 123, 39, 102, 111, 111, 39, 58, 39, 98, 97, 114, 39, 125, 54, 244, 128, 160, - ]), - decoded: { - headers: { - "event-type": { - type: "integer", - value: 40972, - }, - }, - body: Uint8Array.from([123, 39, 102, 111, 111, 39, 58, 39, 98, 97, 114, 39, 125]), - }, - }, - payload_no_headers: { - expectation: "success", - encoded: Uint8Array.from([ - 0, 0, 0, 29, 0, 0, 0, 0, 253, 82, 140, 90, 123, 39, 102, 111, 111, 39, 58, 39, 98, 97, 114, 39, 125, 195, 101, 57, - 54, - ]), - decoded: { - headers: {}, - body: Uint8Array.from([123, 39, 102, 111, 111, 39, 58, 39, 98, 97, 114, 39, 125]), - }, - }, - payload_one_str_header: { - expectation: "success", - encoded: Uint8Array.from([ - 0, 0, 0, 61, 0, 0, 0, 32, 7, 253, 131, 150, 12, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, - 16, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 123, 39, 102, 111, 111, 39, 58, - 39, 98, 97, 114, 39, 125, 141, 156, 8, 177, - ]), - decoded: { - headers: { - "content-type": { - type: "string", - value: "application/json", - }, - }, - body: Uint8Array.from([123, 39, 102, 111, 111, 39, 58, 39, 98, 97, 114, 39, 125]), - }, - }, - corrupted_headers: { - expectation: "failure", - encoded: Uint8Array.from([ - 0, 0, 0, 61, 0, 0, 0, 32, 7, 253, 131, 150, 12, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, - 16, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 123, 97, 102, 111, 111, 39, 58, - 39, 98, 97, 114, 39, 125, 141, 156, 8, 177, - ]), - }, - corrupted_header_len: { - expectation: "failure", - encoded: Uint8Array.from([ - 0, 0, 0, 61, 0, 0, 0, 33, 7, 253, 131, 150, 12, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, - 16, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 123, 39, 102, 111, 111, 39, 58, - 39, 98, 97, 114, 39, 125, 141, 156, 8, 177, - ]), - }, - corrupted_length: { - expectation: "failure", - encoded: Uint8Array.from([ - 0, 0, 0, 62, 0, 0, 0, 32, 7, 253, 131, 150, 12, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, - 16, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 123, 39, 102, 111, 111, 39, 58, - 39, 98, 97, 114, 39, 125, 141, 156, 8, 177, - ]), - }, - corrupted_payload: { - expectation: "failure", - encoded: Uint8Array.from([ - 0, 0, 0, 29, 0, 0, 0, 0, 253, 82, 140, 90, 91, 39, 102, 111, 111, 39, 58, 39, 98, 97, 114, 39, 125, 195, 101, 57, - 54, - ]), - }, -}; diff --git a/packages/eventstream-codec/src/index.ts b/packages/eventstream-codec/src/index.ts index 458feabc1542..5c9006a50799 100644 --- a/packages/eventstream-codec/src/index.ts +++ b/packages/eventstream-codec/src/index.ts @@ -1,8 +1 @@ -export * from "./EventStreamCodec"; -export * from "./HeaderMarshaller"; -export * from "./Int64"; -export * from "./Message"; -export * from "./MessageDecoderStream"; -export * from "./MessageEncoderStream"; -export * from "./SmithyMessageDecoderStream"; -export * from "./SmithyMessageEncoderStream"; +export * from "@smithy/eventstream-codec"; diff --git a/packages/eventstream-codec/src/splitMessage.spec.ts b/packages/eventstream-codec/src/splitMessage.spec.ts deleted file mode 100644 index 0a1627d4b89e..000000000000 --- a/packages/eventstream-codec/src/splitMessage.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { splitMessage } from "./splitMessage"; - -describe("splitMessage", () => { - it("should throw when given a message under 16 bytes", () => { - for (let i = 0; i < 16; i++) { - const emptyMessage = new Uint8Array(i); - expect(() => splitMessage(emptyMessage)).toThrowError("too short"); - } - }); - - it("should throw if the specified length does not match the length of the received message", () => { - const message = new DataView(new ArrayBuffer(17)); - message.setUint32(0, 16, false); - - expect(() => splitMessage(message)).toThrowError("length does not match"); - }); - - it("should throw if the prelude checksum does not match the calculated prelude checksum", () => { - const message = new DataView(new ArrayBuffer(16)); - message.setUint32(0, 16, false); - message.setUint32(8, 0x05c248ec, false); - - expect(() => splitMessage(message)).toThrowError("prelude checksum"); - }); - - it("should throw if the message checksum does not match the calculated message checksum", () => { - const message = new DataView(new ArrayBuffer(16)); - message.setUint32(0, 16, false); - message.setUint32(8, 0x05c248eb, false); - message.setUint32(12, 0x7d98c8fe, false); - - expect(() => splitMessage(message)).toThrowError("message checksum"); - }); - - it("should return header and body buffers for messages with well-formed metadata", () => { - const message = new DataView(new ArrayBuffer(16)); - message.setUint32(0, 16, false); - message.setUint32(8, 0x05c248eb, false); - message.setUint32(12, 0x7d98c8ff, false); - - expect(splitMessage(message)).toEqual({ - headers: new DataView(new ArrayBuffer(0)), - body: new Uint8Array(0), - }); - }); -}); diff --git a/packages/eventstream-codec/src/splitMessage.ts b/packages/eventstream-codec/src/splitMessage.ts deleted file mode 100644 index 2176d0cb4347..000000000000 --- a/packages/eventstream-codec/src/splitMessage.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Crc32 } from "@aws-crypto/crc32"; - -// All prelude components are unsigned, 32-bit integers -const PRELUDE_MEMBER_LENGTH = 4; -// The prelude consists of two components -const PRELUDE_LENGTH = PRELUDE_MEMBER_LENGTH * 2; -// Checksums are always CRC32 hashes. -const CHECKSUM_LENGTH = 4; -// Messages must include a full prelude, a prelude checksum, and a message checksum -const MINIMUM_MESSAGE_LENGTH = PRELUDE_LENGTH + CHECKSUM_LENGTH * 2; - -/** - * @internal - */ -export interface MessageParts { - headers: DataView; - body: Uint8Array; -} - -/** - * @internal - */ -export function splitMessage({ byteLength, byteOffset, buffer }: ArrayBufferView): MessageParts { - if (byteLength < MINIMUM_MESSAGE_LENGTH) { - throw new Error("Provided message too short to accommodate event stream message overhead"); - } - - const view = new DataView(buffer, byteOffset, byteLength); - - const messageLength = view.getUint32(0, false); - - if (byteLength !== messageLength) { - throw new Error("Reported message length does not match received message length"); - } - - const headerLength = view.getUint32(PRELUDE_MEMBER_LENGTH, false); - const expectedPreludeChecksum = view.getUint32(PRELUDE_LENGTH, false); - const expectedMessageChecksum = view.getUint32(byteLength - CHECKSUM_LENGTH, false); - - const checksummer = new Crc32().update(new Uint8Array(buffer, byteOffset, PRELUDE_LENGTH)); - if (expectedPreludeChecksum !== checksummer.digest()) { - throw new Error( - `The prelude checksum specified in the message (${expectedPreludeChecksum}) does not match the calculated CRC32 checksum (${checksummer.digest()})` - ); - } - - checksummer.update( - new Uint8Array(buffer, byteOffset + PRELUDE_LENGTH, byteLength - (PRELUDE_LENGTH + CHECKSUM_LENGTH)) - ); - if (expectedMessageChecksum !== checksummer.digest()) { - throw new Error( - `The message checksum (${checksummer.digest()}) did not match the expected value of ${expectedMessageChecksum}` - ); - } - - return { - headers: new DataView(buffer, byteOffset + PRELUDE_LENGTH + CHECKSUM_LENGTH, headerLength), - body: new Uint8Array( - buffer, - byteOffset + PRELUDE_LENGTH + CHECKSUM_LENGTH + headerLength, - messageLength - headerLength - (PRELUDE_LENGTH + CHECKSUM_LENGTH + CHECKSUM_LENGTH) - ), - }; -} diff --git a/packages/eventstream-codec/src/vectorTypes.fixture.ts b/packages/eventstream-codec/src/vectorTypes.fixture.ts deleted file mode 100644 index b1cfdcab08f0..000000000000 --- a/packages/eventstream-codec/src/vectorTypes.fixture.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Message } from "./Message"; - -export interface NegativeTestVector { - expectation: "failure"; - encoded: Uint8Array; -} - -export interface PositiveTestVector { - expectation: "success"; - encoded: Uint8Array; - decoded: Message; -} - -export type TestVector = NegativeTestVector | PositiveTestVector; - -export type TestVectors = Record; diff --git a/packages/eventstream-codec/test_vectors/decoded/negative/corrupted_header_len b/packages/eventstream-codec/test_vectors/decoded/negative/corrupted_header_len deleted file mode 100644 index 73e4d6f1f132..000000000000 --- a/packages/eventstream-codec/test_vectors/decoded/negative/corrupted_header_len +++ /dev/null @@ -1 +0,0 @@ -Prelude checksum mismatch \ No newline at end of file diff --git a/packages/eventstream-codec/test_vectors/decoded/negative/corrupted_headers b/packages/eventstream-codec/test_vectors/decoded/negative/corrupted_headers deleted file mode 100644 index f56e5c887b58..000000000000 --- a/packages/eventstream-codec/test_vectors/decoded/negative/corrupted_headers +++ /dev/null @@ -1 +0,0 @@ -Message checksum mismatch \ No newline at end of file diff --git a/packages/eventstream-codec/test_vectors/decoded/negative/corrupted_length b/packages/eventstream-codec/test_vectors/decoded/negative/corrupted_length deleted file mode 100644 index 73e4d6f1f132..000000000000 --- a/packages/eventstream-codec/test_vectors/decoded/negative/corrupted_length +++ /dev/null @@ -1 +0,0 @@ -Prelude checksum mismatch \ No newline at end of file diff --git a/packages/eventstream-codec/test_vectors/decoded/negative/corrupted_payload b/packages/eventstream-codec/test_vectors/decoded/negative/corrupted_payload deleted file mode 100644 index f56e5c887b58..000000000000 --- a/packages/eventstream-codec/test_vectors/decoded/negative/corrupted_payload +++ /dev/null @@ -1 +0,0 @@ -Message checksum mismatch \ No newline at end of file diff --git a/packages/eventstream-codec/test_vectors/decoded/positive/all_headers b/packages/eventstream-codec/test_vectors/decoded/positive/all_headers deleted file mode 100644 index fd8f96b88609..000000000000 --- a/packages/eventstream-codec/test_vectors/decoded/positive/all_headers +++ /dev/null @@ -1,58 +0,0 @@ -{ - "total_length": 204, - "headers_length": 175, - "prelude_crc": 263087306, - "headers": [ { - "name": "event-type", - "type": 4, - "value": 40972 - }, - { - "name": "content-type", - "type": 7, - "value": "YXBwbGljYXRpb24vanNvbg==" - }, - { - "name": "bool false", - "type": 1, - "value": false - }, - { - "name": "bool true", - "type": 0, - "value": true - }, - { - "name": "byte", - "type": 2, - "value": -49 - }, - { - "name": "byte buf", - "type": 6, - "value": "SSdtIGEgbGl0dGxlIHRlYXBvdCE=" - }, - { - "name": "timestamp", - "type": 8, - "value": 8675309 - }, - { - "name": "int16", - "type": 3, - "value": 42 - }, - { - "name": "int64", - "type": 5, - "value": 42424242 - }, - { - "name": "uuid", - "type": 9, - "value": "AQIDBAUGBwgJCgsMDQ4PEA==" - } - ], - "payload": "eydmb28nOidiYXInfQ==", - "message_crc": -1415188212 -} diff --git a/packages/eventstream-codec/test_vectors/decoded/positive/empty_message b/packages/eventstream-codec/test_vectors/decoded/positive/empty_message deleted file mode 100644 index 1d35df8e6091..000000000000 --- a/packages/eventstream-codec/test_vectors/decoded/positive/empty_message +++ /dev/null @@ -1,8 +0,0 @@ -{ - "total_length": 16, - "headers_length": 0, - "prelude_crc": 96618731, - "headers": [ ], - "payload": "", - "message_crc": 2107164927 -} diff --git a/packages/eventstream-codec/test_vectors/decoded/positive/int32_header b/packages/eventstream-codec/test_vectors/decoded/positive/int32_header deleted file mode 100644 index 852a0db6e36f..000000000000 --- a/packages/eventstream-codec/test_vectors/decoded/positive/int32_header +++ /dev/null @@ -1,13 +0,0 @@ -{ - "total_length": 45, - "headers_length": 16, - "prelude_crc": 1103373496, - "headers": [ { - "name": "event-type", - "type": 4, - "value": 40972 - } - ], - "payload": "eydmb28nOidiYXInfQ==", - "message_crc": 921993376 -} diff --git a/packages/eventstream-codec/test_vectors/decoded/positive/payload_no_headers b/packages/eventstream-codec/test_vectors/decoded/positive/payload_no_headers deleted file mode 100644 index 1c96631caf50..000000000000 --- a/packages/eventstream-codec/test_vectors/decoded/positive/payload_no_headers +++ /dev/null @@ -1,8 +0,0 @@ -{ - "total_length": 29, - "headers_length": 0, - "prelude_crc": -44921766, - "headers": [ ], - "payload": "eydmb28nOidiYXInfQ==", - "message_crc": -1016776394 -} diff --git a/packages/eventstream-codec/test_vectors/decoded/positive/payload_one_str_header b/packages/eventstream-codec/test_vectors/decoded/positive/payload_one_str_header deleted file mode 100644 index e3bfd312fda4..000000000000 --- a/packages/eventstream-codec/test_vectors/decoded/positive/payload_one_str_header +++ /dev/null @@ -1,13 +0,0 @@ -{ - "total_length": 61, - "headers_length": 32, - "prelude_crc": 134054806, - "headers": [ { - "name": "content-type", - "type": 7, - "value": "YXBwbGljYXRpb24vanNvbg==" - } - ], - "payload": "eydmb28nOidiYXInfQ==", - "message_crc": -1919153999 -} diff --git a/packages/eventstream-codec/test_vectors/encoded/negative/corrupted_header_len b/packages/eventstream-codec/test_vectors/encoded/negative/corrupted_header_len deleted file mode 100644 index 474929c83bea787b066204a8f83b3dd34a80ca29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61 zcmZQzV6bIiU{GZL+dPdYIX|x?HLpasq_QBDok1Y6pdcqRIk6-&KTkiaI6tpiJuN?9 Q-AX+vu}HnPcMiu!05M7wF8}}l diff --git a/packages/eventstream-codec/test_vectors/encoded/negative/corrupted_headers b/packages/eventstream-codec/test_vectors/encoded/negative/corrupted_headers deleted file mode 100644 index 802a2276c15890de8b9f878eeae079f829145a55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61 zcmZQzV6bIiU{GNH+dPdYIX|x?HLpasq_QBDok1Y6pdcqRIk6-&KTkiaI6tpCF)cq| Q-AX+vu}HnPcMiu!05r4|XaE2J diff --git a/packages/eventstream-codec/test_vectors/encoded/negative/corrupted_length b/packages/eventstream-codec/test_vectors/encoded/negative/corrupted_length deleted file mode 100644 index 4e55a3196fc0ad1b6589657c09de3d01071d5729..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61 zcmZQzV6bCgU{GNH+dPdYIX|x?HLpasq_QBDok1Y6pdcqRIk6-&KTkiaI6tpiJuN?9 Q-AX+vu}HnPcMiu!05MJ!F8}}l diff --git a/packages/eventstream-codec/test_vectors/encoded/negative/corrupted_payload b/packages/eventstream-codec/test_vectors/encoded/negative/corrupted_payload deleted file mode 100644 index 2ee9ef3ce1ac8cc3b8afaf579bdb6b38fcaf097e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29 icmZQzV31`1g1Pd-3>a~YcEzJO6$OsVt diff --git a/packages/eventstream-codec/test_vectors/encoded/positive/all_headers b/packages/eventstream-codec/test_vectors/encoded/positive/all_headers deleted file mode 100644 index b986af1489af89dfbb32e59b274c37b307f3ba70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 204 zcmZQzU^v6Tz_6ZwUCJr0)UwpP65W!@f>ag;h6Ox4$@zIDFcEeJfy9D>oXq6JlFa-( z{jB2rJg%ht{2Ya}#GK+(Mouuhq^LBNfhDQ3B$eqr2Z&NgDotZ!5b;#cRY+9G$t)?! zNmVFGO)SVSQRFPi%uOvWNz5(a06Ktyp(Xw;Yi3@Fp&2uS7KmYH!U_^*Y7gJUQd*js n!pX?Q%)-jX&cVsW&BM#bFHo(XmY=U~rJj^nq+Yvv=|>&_SX(=j diff --git a/packages/eventstream-codec/test_vectors/encoded/positive/empty_message b/packages/eventstream-codec/test_vectors/encoded/positive/empty_message deleted file mode 100644 index 663632857e51765759693d08ec460cea9e60a44d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 WcmZQzU=Uyc0@gzwuWM(V_zwUQ=mh5g diff --git a/packages/eventstream-codec/test_vectors/encoded/positive/int32_header b/packages/eventstream-codec/test_vectors/encoded/positive/int32_header deleted file mode 100644 index 4e13b503dbb54f329ed22126b6c35961bd820ba6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 zcmZQzV9;e?U=VOTqOyZ4wJbHSM7N}}AeDuIVF6FIdRl(Ix|MoTVv%~S*_Va|0RGes AdjJ3c diff --git a/packages/eventstream-codec/test_vectors/encoded/positive/payload_no_headers b/packages/eventstream-codec/test_vectors/encoded/positive/payload_no_headers deleted file mode 100644 index 47733a111893568d229a73bfef3be6ba93db48aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29 icmZQzV31`1g1Pd-3>a~YcEzJO8=m;?Y diff --git a/packages/eventstream-codec/test_vectors/encoded/positive/payload_one_str_header b/packages/eventstream-codec/test_vectors/encoded/positive/payload_one_str_header deleted file mode 100644 index d4abaa7f8b4eb25c7683940c779c314902aae08e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61 zcmZQzV6bIiU{GNH+dPdYIX|x?HLpasq_QBDok1Y6pdcqRIk6-&KTkiaI6tpiJuN?9 Q-AX+vu}HnPcMiu!05KF4E&u=k diff --git a/packages/eventstream-serde-browser/package.json b/packages/eventstream-serde-browser/package.json index 1aff5ef4019a..b32f7ba0e58f 100644 --- a/packages/eventstream-serde-browser/package.json +++ b/packages/eventstream-serde-browser/package.json @@ -20,8 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/eventstream-serde-universal": "*", - "@aws-sdk/types": "*", + "@smithy/eventstream-serde-browser": "^1.0.1", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/eventstream-serde-browser/src/EventStreamMarshaller.ts b/packages/eventstream-serde-browser/src/EventStreamMarshaller.ts deleted file mode 100644 index b317870a0e5d..000000000000 --- a/packages/eventstream-serde-browser/src/EventStreamMarshaller.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { EventStreamMarshaller as UniversalEventStreamMarshaller } from "@aws-sdk/eventstream-serde-universal"; -import { Decoder, Encoder, EventStreamMarshaller as IEventStreamMarshaller, Message } from "@aws-sdk/types"; - -import { iterableToReadableStream, readableStreamtoIterable } from "./utils"; - -/** - * @internal - */ -export interface EventStreamMarshaller extends IEventStreamMarshaller {} - -/** - * @internal - */ -export interface EventStreamMarshallerOptions { - utf8Encoder: Encoder; - utf8Decoder: Decoder; -} - -/** - * @internal - * - * Utility class used to serialize and deserialize event streams in - * browsers and ReactNative. - * - * In browsers where ReadableStream API is available: - * * deserialize from ReadableStream to an async iterable of output structure - * * serialize from async iterable of input structure to ReadableStream - * In ReactNative where only async iterable API is available: - * * deserialize from async iterable of binaries to async iterable of output structure - * * serialize from async iterable of input structure to async iterable of binaries - * - * We use ReadableStream API in browsers because of the consistency with other - * streaming operations, where ReadableStream API is used to denote streaming data. - * Whereas in ReactNative, ReadableStream API is not available, we use async iterable - * for streaming data although it has lower throughput. - */ -export class EventStreamMarshaller { - private readonly universalMarshaller: UniversalEventStreamMarshaller; - constructor({ utf8Encoder, utf8Decoder }: EventStreamMarshallerOptions) { - this.universalMarshaller = new UniversalEventStreamMarshaller({ - utf8Decoder, - utf8Encoder, - }); - } - - deserialize( - body: ReadableStream | AsyncIterable, - deserializer: (input: Record) => Promise - ): AsyncIterable { - const bodyIterable = isReadableStream(body) ? readableStreamtoIterable(body) : body; - return this.universalMarshaller.deserialize(bodyIterable, deserializer); - } - - /** - * Generate a stream that serialize events into stream of binary chunks; - * - * Caveat is that streaming request payload doesn't work on browser with native - * xhr or fetch handler currently because they don't support upload streaming. - * reference: - * * https://bugs.chromium.org/p/chromium/issues/detail?id=688906 - * * https://bugzilla.mozilla.org/show_bug.cgi?id=1387483 - * - */ - serialize(input: AsyncIterable, serializer: (event: T) => Message): ReadableStream | AsyncIterable { - const serialziedIterable = this.universalMarshaller.serialize(input, serializer); - return typeof ReadableStream === "function" ? iterableToReadableStream(serialziedIterable) : serialziedIterable; - } -} - -const isReadableStream = (body: any): body is ReadableStream => - typeof ReadableStream === "function" && body instanceof ReadableStream; diff --git a/packages/eventstream-serde-browser/src/index.ts b/packages/eventstream-serde-browser/src/index.ts index 2fb476ea2b01..49b494d96d3b 100644 --- a/packages/eventstream-serde-browser/src/index.ts +++ b/packages/eventstream-serde-browser/src/index.ts @@ -1,12 +1 @@ -/** - * @internal - */ -export * from "./EventStreamMarshaller"; -/** - * @internal - */ -export * from "./provider"; -/** - * @internal - */ -export * from "./utils"; +export * from "@smithy/eventstream-serde-browser"; diff --git a/packages/eventstream-serde-browser/src/provider.ts b/packages/eventstream-serde-browser/src/provider.ts deleted file mode 100644 index 98b399cd9eba..000000000000 --- a/packages/eventstream-serde-browser/src/provider.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Decoder, Encoder, EventSigner, EventStreamSerdeProvider, Provider } from "@aws-sdk/types"; - -import { EventStreamMarshaller } from "./EventStreamMarshaller"; - -/** browser event stream serde utils provider */ -export const eventStreamSerdeProvider: EventStreamSerdeProvider = (options: { - utf8Encoder: Encoder; - utf8Decoder: Decoder; - eventSigner: EventSigner | Provider; -}) => new EventStreamMarshaller(options); diff --git a/packages/eventstream-serde-browser/src/utils.ts b/packages/eventstream-serde-browser/src/utils.ts deleted file mode 100644 index 708e32559749..000000000000 --- a/packages/eventstream-serde-browser/src/utils.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @internal - * - * A util function converting ReadableStream into an async iterable. - * Reference: https://jakearchibald.com/2017/async-iterators-and-generators/#making-streams-iterate - */ -export const readableStreamtoIterable = (readableStream: ReadableStream): AsyncIterable => ({ - [Symbol.asyncIterator]: async function* () { - const reader = readableStream.getReader(); - try { - while (true) { - const { done, value } = await reader.read(); - if (done) return; - yield value as T; - } - } finally { - reader.releaseLock(); - } - }, -}); - -/** - * @internal - * - * A util function converting async iterable to a ReadableStream. - */ -export const iterableToReadableStream = (asyncIterable: AsyncIterable): ReadableStream => { - const iterator = asyncIterable[Symbol.asyncIterator](); - return new ReadableStream({ - async pull(controller) { - const { done, value } = await iterator.next(); - if (done) { - return controller.close(); - } - controller.enqueue(value); - }, - }); -}; diff --git a/packages/eventstream-serde-config-resolver/jest.config.js b/packages/eventstream-serde-config-resolver/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/eventstream-serde-config-resolver/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/eventstream-serde-config-resolver/package.json b/packages/eventstream-serde-config-resolver/package.json index 83c70c874058..73b7e69b183b 100644 --- a/packages/eventstream-serde-config-resolver/package.json +++ b/packages/eventstream-serde-config-resolver/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,7 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "*", + "@smithy/eventstream-serde-config-resolver": "^1.0.1", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/eventstream-serde-config-resolver/src/EventStreamSerdeConfig.spec.ts b/packages/eventstream-serde-config-resolver/src/EventStreamSerdeConfig.spec.ts deleted file mode 100644 index c89eb402b5ac..000000000000 --- a/packages/eventstream-serde-config-resolver/src/EventStreamSerdeConfig.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { resolveEventStreamSerdeConfig } from "./EventStreamSerdeConfig"; - -describe("resolveEventStreamSerdeConfig", () => { - const eventStreamSerdeProvider = jest.fn(); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("sets value returned by eventStreamSerdeProvider", () => { - const mockReturn = "mockReturn"; - eventStreamSerdeProvider.mockReturnValueOnce(mockReturn); - - const input = { eventStreamSerdeProvider }; - expect(resolveEventStreamSerdeConfig(input).eventStreamMarshaller).toStrictEqual(mockReturn); - expect(eventStreamSerdeProvider).toHaveBeenCalledTimes(1); - expect(eventStreamSerdeProvider).toHaveBeenCalledWith(input); - }); -}); diff --git a/packages/eventstream-serde-config-resolver/src/EventStreamSerdeConfig.ts b/packages/eventstream-serde-config-resolver/src/EventStreamSerdeConfig.ts deleted file mode 100644 index 39db1a07fb5e..000000000000 --- a/packages/eventstream-serde-config-resolver/src/EventStreamSerdeConfig.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { EventStreamMarshaller, EventStreamSerdeProvider } from "@aws-sdk/types"; - -/** - * @internal - */ -export interface EventStreamSerdeInputConfig {} - -/** - * @internal - */ -export interface EventStreamSerdeResolvedConfig { - eventStreamMarshaller: EventStreamMarshaller; -} - -interface PreviouslyResolved { - /** - * Provide the event stream marshaller for the given runtime - * @internal - */ - eventStreamSerdeProvider: EventStreamSerdeProvider; -} - -/** - * @internal - */ -export const resolveEventStreamSerdeConfig = ( - input: T & PreviouslyResolved & EventStreamSerdeInputConfig -): T & EventStreamSerdeResolvedConfig => ({ - ...input, - eventStreamMarshaller: input.eventStreamSerdeProvider(input), -}); diff --git a/packages/eventstream-serde-config-resolver/src/index.ts b/packages/eventstream-serde-config-resolver/src/index.ts index 49ec397c465a..4d686dfb8267 100644 --- a/packages/eventstream-serde-config-resolver/src/index.ts +++ b/packages/eventstream-serde-config-resolver/src/index.ts @@ -1,4 +1 @@ -/** - * @internal - */ -export * from "./EventStreamSerdeConfig"; +export * from "@smithy/eventstream-serde-config-resolver"; diff --git a/packages/eventstream-serde-node/jest.config.js b/packages/eventstream-serde-node/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/eventstream-serde-node/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/eventstream-serde-node/package.json b/packages/eventstream-serde-node/package.json index cd6b45d2ed9b..adaee34e6342 100644 --- a/packages/eventstream-serde-node/package.json +++ b/packages/eventstream-serde-node/package.json @@ -20,8 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/eventstream-serde-universal": "*", - "@aws-sdk/types": "*", + "@smithy/eventstream-codec": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/eventstream-serde-node/src/EventStreamMarshaller.ts b/packages/eventstream-serde-node/src/EventStreamMarshaller.ts deleted file mode 100644 index 200b0bdf24e0..000000000000 --- a/packages/eventstream-serde-node/src/EventStreamMarshaller.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { EventStreamMarshaller as UniversalEventStreamMarshaller } from "@aws-sdk/eventstream-serde-universal"; -import { Decoder, Encoder, EventStreamMarshaller as IEventStreamMarshaller, Message } from "@aws-sdk/types"; -import { Readable } from "stream"; - -import { readabletoIterable } from "./utils"; - -/** - * @internal - */ -export interface EventStreamMarshaller extends IEventStreamMarshaller {} - -/** - * @internal - */ -export interface EventStreamMarshallerOptions { - utf8Encoder: Encoder; - utf8Decoder: Decoder; -} - -/** - * @internal - */ -export class EventStreamMarshaller { - private readonly universalMarshaller: UniversalEventStreamMarshaller; - constructor({ utf8Encoder, utf8Decoder }: EventStreamMarshallerOptions) { - this.universalMarshaller = new UniversalEventStreamMarshaller({ - utf8Decoder, - utf8Encoder, - }); - } - - deserialize(body: Readable, deserializer: (input: Record) => Promise): AsyncIterable { - //should use stream[Symbol.asyncIterable] when the api is stable - //reference: https://nodejs.org/docs/latest-v11.x/api/stream.html#stream_readable_symbol_asynciterator - const bodyIterable: AsyncIterable = - typeof body[Symbol.asyncIterator] === "function" ? body : readabletoIterable(body); - return this.universalMarshaller.deserialize(bodyIterable, deserializer); - } - - serialize(input: AsyncIterable, serializer: (event: T) => Message): Readable { - return Readable.from(this.universalMarshaller.serialize(input, serializer)); - } -} diff --git a/packages/eventstream-serde-node/src/index.ts b/packages/eventstream-serde-node/src/index.ts index 9f8e9f7e33a8..ca1b26f37b37 100644 --- a/packages/eventstream-serde-node/src/index.ts +++ b/packages/eventstream-serde-node/src/index.ts @@ -1,8 +1 @@ -/** - * @internal - */ -export * from "./EventStreamMarshaller"; -/** - * @internal - */ -export * from "./provider"; +export * from "@smithy/eventstream-serde-node"; diff --git a/packages/eventstream-serde-node/src/provider.ts b/packages/eventstream-serde-node/src/provider.ts deleted file mode 100644 index 853777765a84..000000000000 --- a/packages/eventstream-serde-node/src/provider.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Decoder, Encoder, EventSigner, EventStreamSerdeProvider, Provider } from "@aws-sdk/types"; - -import { EventStreamMarshaller } from "./EventStreamMarshaller"; - -/** NodeJS event stream utils provider */ -export const eventStreamSerdeProvider: EventStreamSerdeProvider = (options: { - utf8Encoder: Encoder; - utf8Decoder: Decoder; - eventSigner: EventSigner | Provider; -}) => new EventStreamMarshaller(options); diff --git a/packages/eventstream-serde-node/src/utils.ts b/packages/eventstream-serde-node/src/utils.ts deleted file mode 100644 index 2c799cd729ac..000000000000 --- a/packages/eventstream-serde-node/src/utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Readable } from "stream"; - -/** - * Convert object stream piped in into an async iterable. This - * daptor should be deprecated when Node stream iterator is stable. - * Caveat: this adaptor won't have backpressure to inwards stream - * - * Reference: https://nodejs.org/docs/latest-v11.x/api/stream.html#stream_readable_symbol_asynciterator - */ - -/** - * @internal - */ -export async function* readabletoIterable(readStream: Readable): AsyncIterable { - let streamEnded = false; - let generationEnded = false; - const records = new Array(); - - readStream.on("error", (err) => { - if (!streamEnded) { - streamEnded = true; - } - if (err) { - throw err; - } - }); - - readStream.on("data", (data) => { - records.push(data); - }); - - readStream.on("end", () => { - streamEnded = true; - }); - - while (!generationEnded) { - // @ts-ignore TS2345: Argument of type 'T | undefined' is not assignable to type 'T | PromiseLike'. - const value = await new Promise((resolve) => setTimeout(() => resolve(records.shift()), 0)); - if (value) { - yield value; - } - generationEnded = streamEnded && records.length === 0; - } -} diff --git a/packages/eventstream-serde-universal/jest.config.js b/packages/eventstream-serde-universal/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/eventstream-serde-universal/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/eventstream-serde-universal/package.json b/packages/eventstream-serde-universal/package.json index f1e9562dba70..1a91e2b53b2a 100644 --- a/packages/eventstream-serde-universal/package.json +++ b/packages/eventstream-serde-universal/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,8 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/eventstream-codec": "*", - "@aws-sdk/types": "*", + "@smithy/eventstream-serde-universal": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/eventstream-serde-universal/src/EventStreamMarshaller.ts b/packages/eventstream-serde-universal/src/EventStreamMarshaller.ts deleted file mode 100644 index 7fac6b16e859..000000000000 --- a/packages/eventstream-serde-universal/src/EventStreamMarshaller.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - EventStreamCodec, - MessageDecoderStream, - MessageEncoderStream, - SmithyMessageDecoderStream, - SmithyMessageEncoderStream, -} from "@aws-sdk/eventstream-codec"; -import { Decoder, Encoder, EventStreamMarshaller as IEventStreamMarshaller, Message } from "@aws-sdk/types"; - -import { getChunkedStream } from "./getChunkedStream"; -import { getMessageUnmarshaller } from "./getUnmarshalledStream"; - -/** - * @internal - */ -export interface EventStreamMarshaller extends IEventStreamMarshaller {} - -/** - * @internal - */ -export interface EventStreamMarshallerOptions { - utf8Encoder: Encoder; - utf8Decoder: Decoder; -} - -/** - * @internal - */ -export class EventStreamMarshaller { - private readonly eventStreamCodec: EventStreamCodec; - private readonly utfEncoder: Encoder; - - constructor({ utf8Encoder, utf8Decoder }: EventStreamMarshallerOptions) { - this.eventStreamCodec = new EventStreamCodec(utf8Encoder, utf8Decoder); - this.utfEncoder = utf8Encoder; - } - - deserialize( - body: AsyncIterable, - deserializer: (input: Record) => Promise - ): AsyncIterable { - const inputStream = getChunkedStream(body); - // @ts-expect-error Type 'SmithyMessageDecoderStream>' is not assignable to type 'AsyncIterable' - return new SmithyMessageDecoderStream({ - messageStream: new MessageDecoderStream({ inputStream, decoder: this.eventStreamCodec }), - // @ts-expect-error Type 'T' is not assignable to type 'Record' - deserializer: getMessageUnmarshaller(deserializer, this.utfEncoder), - }); - } - - serialize(inputStream: AsyncIterable, serializer: (event: T) => Message): AsyncIterable { - return new MessageEncoderStream({ - messageStream: new SmithyMessageEncoderStream({ inputStream, serializer }), - encoder: this.eventStreamCodec, - includeEndFrame: true, - }); - } -} diff --git a/packages/eventstream-serde-universal/src/fixtures/MockEventMessageSource.fixture.ts b/packages/eventstream-serde-universal/src/fixtures/MockEventMessageSource.fixture.ts deleted file mode 100644 index 1e74af14dcc3..000000000000 --- a/packages/eventstream-serde-universal/src/fixtures/MockEventMessageSource.fixture.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Readable, ReadableOptions } from "stream"; - -/** - * @internal - */ -export interface MockEventMessageSourceOptions extends ReadableOptions { - messages: Array; - emitSize: number; - throwError?: Error; -} - -/** - * @internal - */ -export class MockEventMessageSource extends Readable { - private readonly data: Buffer; - private readonly emitSize: number; - private readonly throwError?: Error; - private readCount = 0; - constructor(options: MockEventMessageSourceOptions) { - super(options); - this.data = Buffer.concat(options.messages); - this.emitSize = options.emitSize; - this.throwError = options.throwError; - } - - _read() { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const self = this; - if (this.readCount === this.data.length) { - if (this.throwError) { - process.nextTick(function () { - self.emit("error", new Error("Throwing an error!")); - }); - return; - } else { - this.push(null); - return; - } - } - - const bytesLeft = this.data.length - this.readCount; - const numBytesToSend = Math.min(bytesLeft, this.emitSize); - - const chunk = this.data.slice(this.readCount, this.readCount + numBytesToSend); - this.readCount += numBytesToSend; - this.push(chunk); - } -} diff --git a/packages/eventstream-serde-universal/src/fixtures/event.fixture.ts b/packages/eventstream-serde-universal/src/fixtures/event.fixture.ts deleted file mode 100644 index bfe5dd876de1..000000000000 --- a/packages/eventstream-serde-universal/src/fixtures/event.fixture.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @internal - */ -export const recordEventMessage = Buffer.from( - "AAAA0AAAAFX31gVLDTptZXNzYWdlLXR5cGUHAAVldmVudAs6ZXZlbnQtdHlwZQcAB1JlY29yZHMNOmNvbnRlbnQtdHlwZQcAGGFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbTEsRm9vLFdoZW4gbGlmZSBnaXZlcyB5b3UgZm9vLi4uCjIsQmFyLG1ha2UgQmFyIQozLEZpenosU29tZXRpbWVzIHBhaXJlZCB3aXRoLi4uCjQsQnV6eix0aGUgaW5mYW1vdXMgQnV6eiEKzxKeSw==", - "base64" -); - -/** - * @internal - */ -export const statsEventMessage = Buffer.from( - "AAAA0QAAAEM+YpmqDTptZXNzYWdlLXR5cGUHAAVldmVudAs6ZXZlbnQtdHlwZQcABVN0YXRzDTpjb250ZW50LXR5cGUHAAh0ZXh0L3htbDxTdGF0cyB4bWxucz0iIj48Qnl0ZXNTY2FubmVkPjEyNjwvQnl0ZXNTY2FubmVkPjxCeXRlc1Byb2Nlc3NlZD4xMjY8L0J5dGVzUHJvY2Vzc2VkPjxCeXRlc1JldHVybmVkPjEwNzwvQnl0ZXNSZXR1cm5lZD48L1N0YXRzPiJ0pLk=", - "base64" -); - -/** - * @internal - */ -export const endEventMessage = Buffer.from( - "AAAAOAAAACjBxoTUDTptZXNzYWdlLXR5cGUHAAVldmVudAs6ZXZlbnQtdHlwZQcAA0VuZM+X05I=", - "base64" -); - -/** - * @internal - */ -export const exception = Buffer.from( - "AAAAtgAAAF8BcW64DTpjb250ZW50LXR5cGUHABhhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0NOm1lc3NhZ2UtdHlwZQcACWV4Y2VwdGlvbg86ZXhjZXB0aW9uLXR5cGUHAAlFeGNlcHRpb25UaGlzIGlzIGEgbW9kZWxlZCBleGNlcHRpb24gZXZlbnQgdGhhdCB3b3VsZCBiZSB0aHJvd24gaW4gZGVzZXJpYWxpemVyLj6Gc60=", - "base64" -); diff --git a/packages/eventstream-serde-universal/src/getChunkedStream.spec.ts b/packages/eventstream-serde-universal/src/getChunkedStream.spec.ts deleted file mode 100644 index 31adac9f26f5..000000000000 --- a/packages/eventstream-serde-universal/src/getChunkedStream.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { endEventMessage, recordEventMessage, statsEventMessage } from "./fixtures/event.fixture"; -import { MockEventMessageSource } from "./fixtures/MockEventMessageSource.fixture"; -import { getChunkedStream } from "./getChunkedStream"; - -describe("getChunkedStream", () => { - it("splits payloads into individual messages", async () => { - const messages = []; - const mockMessages = [recordEventMessage, statsEventMessage, endEventMessage]; - const mockStream = new MockEventMessageSource({ - messages: mockMessages, - emitSize: 100, - }); - const chunkerStream = getChunkedStream(mockStream); - for await (const msg of chunkerStream) { - messages.push(msg); - } - expect(messages.length).toBe(3); - }); - - it("splits payloads in correct order", async () => { - const messages: Array = []; - const mockMessages = [recordEventMessage, statsEventMessage, recordEventMessage, endEventMessage]; - const mockStream = new MockEventMessageSource({ - messages: mockMessages, - emitSize: 100, - }); - const chunkerStream = getChunkedStream(mockStream); - for await (const msg of chunkerStream) { - messages.push(msg); - } - expect(messages.length).toBe(4); - for (let i = 0; i < mockMessages.length; i++) { - expect(Buffer.from(messages[i]).toString("base64")).toEqual(mockMessages[i].toString("base64")); - } - }); - - it("splits payloads when received all at once", async () => { - const messages = []; - const mockMessages = [recordEventMessage, statsEventMessage, endEventMessage]; - const mockStream = new MockEventMessageSource({ - messages: mockMessages, - emitSize: mockMessages.reduce((prev, cur) => { - return prev + cur.length; - }, 0), - }); - const chunkerStream = getChunkedStream(mockStream); - for await (const msg of chunkerStream) { - messages.push(msg); - } - expect(messages.length).toBe(3); - }); - - it("splits payloads when total event message length spans multiple chunks", async () => { - const messages = []; - const mockMessages = [recordEventMessage, statsEventMessage, endEventMessage]; - const mockStream = new MockEventMessageSource({ - messages: mockMessages, - emitSize: 1, - }); - const chunkerStream = getChunkedStream(mockStream); - for await (const msg of chunkerStream) { - messages.push(msg); - } - expect(messages.length).toBe(3); - }); - - it("splits payloads when total event message length spans 2 chunks", async () => { - const messages = []; - const mockMessages = [recordEventMessage, statsEventMessage, endEventMessage]; - const mockStream = new MockEventMessageSource({ - messages: mockMessages, - emitSize: recordEventMessage.length + 2, - }); - const chunkerStream = getChunkedStream(mockStream); - for await (const msg of chunkerStream) { - messages.push(msg); - } - expect(messages.length).toBe(3); - }); - - it("sends an error if an event message is truncated", async () => { - const responseMessage = Buffer.concat([recordEventMessage, statsEventMessage, endEventMessage]); - const mockStream = new MockEventMessageSource({ - messages: [responseMessage.slice(0, responseMessage.length - 4)], - emitSize: 10, - }); - - const chunkerStream = getChunkedStream(mockStream); - let error: Error | undefined = undefined; - try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for await (const msg of chunkerStream) { - //Pass - } - } catch (err) { - error = err; - } - expect(error!.message).toEqual("Truncated event message received."); - }); -}); diff --git a/packages/eventstream-serde-universal/src/getChunkedStream.ts b/packages/eventstream-serde-universal/src/getChunkedStream.ts deleted file mode 100644 index d6af62f6f93e..000000000000 --- a/packages/eventstream-serde-universal/src/getChunkedStream.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @internal - */ -export function getChunkedStream(source: AsyncIterable): AsyncIterable { - let currentMessageTotalLength = 0; - let currentMessagePendingLength = 0; - let currentMessage: Uint8Array | null = null; - let messageLengthBuffer: Uint8Array | null = null; - const allocateMessage = (size: number) => { - if (typeof size !== "number") { - throw new Error("Attempted to allocate an event message where size was not a number: " + size); - } - currentMessageTotalLength = size; - currentMessagePendingLength = 4; - currentMessage = new Uint8Array(size); - const currentMessageView = new DataView(currentMessage.buffer); - currentMessageView.setUint32(0, size, false); //set big-endian Uint32 to 0~3 bytes - }; - - const iterator = async function* () { - const sourceIterator = source[Symbol.asyncIterator](); - while (true) { - const { value, done } = await sourceIterator.next(); - if (done) { - if (!currentMessageTotalLength) { - return; - } else if (currentMessageTotalLength === currentMessagePendingLength) { - yield currentMessage as Uint8Array; - } else { - throw new Error("Truncated event message received."); - } - return; - } - - const chunkLength = value.length; - let currentOffset = 0; - - while (currentOffset < chunkLength) { - // create new message if necessary - if (!currentMessage) { - // working on a new message, determine total length - const bytesRemaining = chunkLength - currentOffset; - // prevent edge case where total length spans 2 chunks - if (!messageLengthBuffer) { - messageLengthBuffer = new Uint8Array(4); - } - const numBytesForTotal = Math.min( - 4 - currentMessagePendingLength, // remaining bytes to fill the messageLengthBuffer - bytesRemaining // bytes left in chunk - ); - - messageLengthBuffer.set( - // @ts-ignore error TS2532: Object is possibly 'undefined' for value - value.slice(currentOffset, currentOffset + numBytesForTotal), - currentMessagePendingLength - ); - - currentMessagePendingLength += numBytesForTotal; - currentOffset += numBytesForTotal; - - if (currentMessagePendingLength < 4) { - // not enough information to create the current message - break; - } - allocateMessage(new DataView(messageLengthBuffer.buffer).getUint32(0, false)); - messageLengthBuffer = null; - } - - // write data into current message - const numBytesToWrite = Math.min( - currentMessageTotalLength - currentMessagePendingLength, // number of bytes left to complete message - chunkLength - currentOffset // number of bytes left in the original chunk - ); - currentMessage!.set( - // @ts-ignore error TS2532: Object is possibly 'undefined' for value - value.slice(currentOffset, currentOffset + numBytesToWrite), - currentMessagePendingLength - ); - currentMessagePendingLength += numBytesToWrite; - currentOffset += numBytesToWrite; - - // check if a message is ready to be pushed - if (currentMessageTotalLength && currentMessageTotalLength === currentMessagePendingLength) { - // push out the message - yield currentMessage as Uint8Array; - // cleanup - currentMessage = null; - currentMessageTotalLength = 0; - currentMessagePendingLength = 0; - } - } - } - }; - - return { - [Symbol.asyncIterator]: iterator, - }; -} diff --git a/packages/eventstream-serde-universal/src/getUnmarshalledStream.spec.ts b/packages/eventstream-serde-universal/src/getUnmarshalledStream.spec.ts deleted file mode 100644 index 9410bc605ed6..000000000000 --- a/packages/eventstream-serde-universal/src/getUnmarshalledStream.spec.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { EventStreamCodec } from "@aws-sdk/eventstream-codec"; -import { Message } from "@aws-sdk/types"; -import { fromUtf8, toUtf8 } from "@aws-sdk/util-utf8"; - -import { endEventMessage, exception, recordEventMessage, statsEventMessage } from "./fixtures/event.fixture"; -import { getUnmarshalledStream } from "./getUnmarshalledStream"; - -describe("getUnmarshalledStream", () => { - it("emits parsed payload on data", async () => { - const expectedMessages: Array = [ - { - headers: { - ":content-type": { - type: "string", - value: "application/octet-stream", - }, - ":event-type": { type: "string", value: "Records" }, - ":message-type": { type: "string", value: "event" }, - }, - body: new Uint8Array( - Buffer.from( - `1,Foo,When life gives you foo...\n2,Bar,make Bar!\n3,Fizz,Sometimes paired with...\n4,Buzz,the infamous Buzz!\n` - ) - ), - }, - { - headers: { - ":content-type": { - type: "string", - value: "text/xml", - }, - ":event-type": { type: "string", value: "Stats" }, - ":message-type": { type: "string", value: "event" }, - }, - body: new Uint8Array( - Buffer.from( - '126126107' - ) - ), - }, - { - headers: { - ":event-type": { type: "string", value: "End" }, - ":message-type": { type: "string", value: "event" }, - }, - body: new Uint8Array(), - }, - ]; - - const source = async function* () { - yield recordEventMessage; - yield statsEventMessage; - yield endEventMessage; - }; - const unmarshallerStream = getUnmarshalledStream(source(), { - eventStreamCodec: new EventStreamCodec(toUtf8, fromUtf8), - deserializer: (message) => Promise.resolve(message), - toUtf8, - }); - const messages: Array = []; - for await (const message of unmarshallerStream) { - messages.push(message[Object.keys(message)[0]]); - } - for (let i = 1; i < messages.length; i++) { - expect(messages[i]).toEqual(expectedMessages[i]); - } - }); - - it("throws when deserializer throws an error", async () => { - const source = { - [Symbol.asyncIterator]: async function* () { - yield exception; - }, - }; - const deserStream = getUnmarshalledStream(source, { - eventStreamCodec: new EventStreamCodec(toUtf8, fromUtf8), - deserializer: () => { - throw new Error("error event"); - }, - toUtf8, - }); - let error: Error | undefined = undefined; - try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for await (const event of deserStream) { - //pass. - } - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error!.message).toEqual("error event"); - }); - - it("throws on exception event if deserializer doesn't throw", async () => { - //This could happened if client-side SDK is not up-to-date - const source = { - [Symbol.asyncIterator]: async function* () { - yield exception; - }, - }; - const deserStream = getUnmarshalledStream(source, { - eventStreamCodec: new EventStreamCodec(toUtf8, fromUtf8), - deserializer: (message) => - Promise.resolve({ - $unknown: message, - }), - toUtf8, - }); - let error: Error | undefined = undefined; - try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for await (const event of deserStream) { - //pass. - } - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error?.name).toEqual("Exception"); - expect(error?.message).toEqual("This is a modeled exception event that would be thrown in deserializer."); - }); - - it("omit the unknown event type", async () => { - const source = async function* () { - yield recordEventMessage; - }; - const unmarshallerStream = getUnmarshalledStream(source(), { - eventStreamCodec: new EventStreamCodec(toUtf8, fromUtf8), - deserializer: (message) => - Promise.resolve({ - $unknown: message, - } as any), //deserializer that parse anything into unknown event - toUtf8, - }); - const messages: Array = []; - for await (const message of unmarshallerStream) { - messages.push(message[Object.keys(message)[0]]); - } - expect(messages.length).toEqual(0); - }); -}); diff --git a/packages/eventstream-serde-universal/src/getUnmarshalledStream.ts b/packages/eventstream-serde-universal/src/getUnmarshalledStream.ts deleted file mode 100644 index e0d5b83687ea..000000000000 --- a/packages/eventstream-serde-universal/src/getUnmarshalledStream.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { EventStreamCodec } from "@aws-sdk/eventstream-codec"; -import { Encoder, Message } from "@aws-sdk/types"; - -/** - * @internal - */ -export type UnmarshalledStreamOptions = { - eventStreamCodec: EventStreamCodec; - deserializer: (input: Record) => Promise; - toUtf8: Encoder; -}; - -/** - * @internal - */ -export function getUnmarshalledStream>( - source: AsyncIterable, - options: UnmarshalledStreamOptions -): AsyncIterable { - const messageUnmarshaller = getMessageUnmarshaller(options.deserializer, options.toUtf8); - return { - [Symbol.asyncIterator]: async function* () { - for await (const chunk of source) { - const message = options.eventStreamCodec.decode(chunk); - const type = await messageUnmarshaller(message); - if (type === undefined) continue; - yield type; - } - }, - }; -} - -/** - * @internal - */ -export function getMessageUnmarshaller>( - deserializer: (input: Record) => Promise, - toUtf8: Encoder -): (input: Message) => Promise { - return async function (message: Message): Promise { - const { value: messageType } = message.headers[":message-type"]; - if (messageType === "error") { - // Unmodeled exception in event - const unmodeledError = new Error((message.headers[":error-message"].value as string) || "UnknownError"); - unmodeledError.name = message.headers[":error-code"].value as string; - throw unmodeledError; - } else if (messageType === "exception") { - // For modeled exception, push it to deserializer and throw after deserializing - const code = message.headers[":exception-type"].value as string; - const exception = { [code]: message }; - // Get parsed exception event in key(error code) value(structured error) pair. - const deserializedException = await deserializer(exception); - if (deserializedException.$unknown) { - //this is an unmodeled exception then try parsing it with best effort - const error = new Error(toUtf8(message.body)); - error.name = code; - throw error; - } - throw deserializedException[code]; - } else if (messageType === "event") { - const event = { - [message.headers[":event-type"].value as string]: message, - }; - const deserialized = await deserializer(event); - if (deserialized.$unknown) return; - return deserialized; - } else { - throw Error(`Unrecognizable event type: ${message.headers[":event-type"].value}`); - } - }; -} diff --git a/packages/eventstream-serde-universal/src/index.ts b/packages/eventstream-serde-universal/src/index.ts index 9f8e9f7e33a8..aa5313572693 100644 --- a/packages/eventstream-serde-universal/src/index.ts +++ b/packages/eventstream-serde-universal/src/index.ts @@ -1,8 +1 @@ -/** - * @internal - */ -export * from "./EventStreamMarshaller"; -/** - * @internal - */ -export * from "./provider"; +export * from "@smithy/eventstream-serde-universal"; diff --git a/packages/eventstream-serde-universal/src/provider.ts b/packages/eventstream-serde-universal/src/provider.ts deleted file mode 100644 index 853777765a84..000000000000 --- a/packages/eventstream-serde-universal/src/provider.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Decoder, Encoder, EventSigner, EventStreamSerdeProvider, Provider } from "@aws-sdk/types"; - -import { EventStreamMarshaller } from "./EventStreamMarshaller"; - -/** NodeJS event stream utils provider */ -export const eventStreamSerdeProvider: EventStreamSerdeProvider = (options: { - utf8Encoder: Encoder; - utf8Decoder: Decoder; - eventSigner: EventSigner | Provider; -}) => new EventStreamMarshaller(options); diff --git a/packages/fetch-http-handler/jest.config.js b/packages/fetch-http-handler/jest.config.js deleted file mode 100644 index e29c0e8a0cde..000000000000 --- a/packages/fetch-http-handler/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, - testEnvironment: "jsdom", - testPathIgnorePatterns: ["/node_modules/", "(.*).browser.spec.js"], -}; diff --git a/packages/fetch-http-handler/karma.conf.js b/packages/fetch-http-handler/karma.conf.js deleted file mode 100644 index 57889c7f4bae..000000000000 --- a/packages/fetch-http-handler/karma.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -// Set up binary for Chromium browser in CHROME_BIN environment variable before running the test - -module.exports = function (config) { - config.set({ - frameworks: ["jasmine", "karma-typescript"], - files: [ - "src/stream-collector.ts", - "src/stream-collector.browser.spec.ts", - "src/fetch-http-handler.ts", - "src/fetch-http-handler.browser.spec.ts", - "src/request-timeout.ts", - ], - exclude: ["**/*.d.ts"], - preprocessors: { - "**/*.ts": "karma-typescript", - }, - reporters: ["progress", "karma-typescript"], - browsers: ["ChromeHeadlessNoSandbox"], - customLaunchers: { - ChromeHeadlessNoSandbox: { - base: "ChromeHeadless", - flags: ["--no-sandbox"], - }, - }, - karmaTypescriptConfig: { - bundlerOptions: { - addNodeGlobals: true, - }, - }, - singleRun: true, - }); -}; diff --git a/packages/fetch-http-handler/package.json b/packages/fetch-http-handler/package.json index 9d04bd104571..3bd5d90e53a6 100644 --- a/packages/fetch-http-handler/package.json +++ b/packages/fetch-http-handler/package.json @@ -11,7 +11,7 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "extract:docs": "api-extractor run --local", - "test": "jest --coverage && karma start karma.conf.js" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", @@ -22,10 +22,7 @@ "module": "./dist-es/index.js", "types": "./dist-types/index.d.ts", "dependencies": { - "@aws-sdk/protocol-http": "*", - "@aws-sdk/querystring-builder": "*", - "@aws-sdk/types": "*", - "@aws-sdk/util-base64": "*", + "@smithy/fetch-http-handler": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/fetch-http-handler/src/fetch-http-handler.browser.spec.ts b/packages/fetch-http-handler/src/fetch-http-handler.browser.spec.ts deleted file mode 100644 index 889bdf635d9f..000000000000 --- a/packages/fetch-http-handler/src/fetch-http-handler.browser.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FetchHttpHandler } from "./fetch-http-handler"; - -describe(FetchHttpHandler.name, () => { - it("calls request without mode included in requestOptions", (done) => { - const fetchHttpHandler = new FetchHttpHandler(); - const spy = spyOn(window, "Request"); - fetchHttpHandler.handle({} as any, {}); - expect(spy.calls.argsFor(0)[1].mode).toEqual(undefined); - done(); - }); -}); diff --git a/packages/fetch-http-handler/src/fetch-http-handler.spec.ts b/packages/fetch-http-handler/src/fetch-http-handler.spec.ts deleted file mode 100644 index 7e5fb2d21ef8..000000000000 --- a/packages/fetch-http-handler/src/fetch-http-handler.spec.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { AbortController } from "@aws-sdk/abort-controller"; -import { HttpRequest } from "@aws-sdk/protocol-http"; - -import { FetchHttpHandler } from "./fetch-http-handler"; -import { requestTimeout } from "./request-timeout"; - -const mockRequest = jest.fn(); -let mockResponse: any; -let timeoutSpy: jest.SpyInstance; - -(global as any).Request = mockRequest; -(global as any).Headers = jest.fn(); - -describe(FetchHttpHandler.name, () => { - beforeEach(() => { - (global as any).AbortController = void 0; - jest.clearAllMocks(); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - mockResponse = { - headers: { - entries: jest.fn().mockReturnValue([ - ["foo", "bar"], - ["bizz", "bazz"], - ]), - }, - arrayBuffer: jest.fn(), - }; - }); - - afterEach(() => { - jest.clearAllTimers(); - if (timeoutSpy) { - timeoutSpy.mockRestore(); - } - }); - - it("makes requests using fetch", async () => { - const mockResponse = { - headers: { - entries: jest.fn().mockReturnValue([ - ["foo", "bar"], - ["bizz", "bazz"], - ]), - }, - blob: jest.fn().mockResolvedValue(new Blob(["FOO"])), - }; - const mockFetch = jest.fn().mockResolvedValue(mockResponse); - - (global as any).fetch = mockFetch; - const fetchHttpHandler = new FetchHttpHandler(); - - const response = await fetchHttpHandler.handle({} as any, {}); - - expect(mockFetch.mock.calls.length).toBe(1); - expect(await blobToText(response.response.body)).toBe("FOO"); - }); - - it("defaults to response.blob for response.body = null", async () => { - const mockResponse = { - body: null, - headers: { - entries: jest.fn().mockReturnValue([ - ["foo", "bar"], - ["bizz", "bazz"], - ]), - }, - blob: jest.fn().mockResolvedValue(new Blob(["FOO"])), - }; - const mockFetch = jest.fn().mockResolvedValue(mockResponse); - - (global as any).fetch = mockFetch; - const fetchHttpHandler = new FetchHttpHandler(); - - const response = await fetchHttpHandler.handle({} as any, {}); - - expect(mockFetch.mock.calls.length).toBe(1); - expect(await blobToText(response.response.body)).toBe("FOO"); - }); - - it("properly constructs url", async () => { - const mockResponse = { - headers: { - entries: jest.fn().mockReturnValue([ - ["foo", "bar"], - ["bizz", "bazz"], - ]), - }, - blob: jest.fn().mockResolvedValue(new Blob()), - }; - const mockFetch = jest.fn().mockResolvedValue(mockResponse); - - (global as any).fetch = mockFetch; - - const httpRequest = new HttpRequest({ - headers: {}, - hostname: "foo.amazonaws.com", - method: "GET", - path: "/test", - query: { bar: "baz" }, - username: "username", - password: "password", - fragment: "fragment", - protocol: "https:", - port: 443, - }); - const fetchHttpHandler = new FetchHttpHandler(); - - await fetchHttpHandler.handle(httpRequest, {}); - - expect(mockFetch.mock.calls.length).toBe(1); - const requestCall = mockRequest.mock.calls[0]; - expect(requestCall[0]).toBe("https://username:password@foo.amazonaws.com:443/test?bar=baz#fragment"); - }); - - it("will omit body if method is GET", async () => { - const mockResponse = { - headers: { entries: jest.fn().mockReturnValue([]) }, - blob: jest.fn().mockResolvedValue(new Blob()), - }; - const mockFetch = jest.fn().mockResolvedValue(mockResponse); - - (global as any).fetch = mockFetch; - - const httpRequest = new HttpRequest({ - headers: {}, - hostname: "foo.amazonaws.com", - method: "GET", - path: "/", - body: "will be omitted", - }); - const fetchHttpHandler = new FetchHttpHandler(); - - await fetchHttpHandler.handle(httpRequest, {}); - - expect(mockFetch.mock.calls.length).toBe(1); - const requestCall = mockRequest.mock.calls[0]; - expect(requestCall[1].body).toBeUndefined(); - }); - - it("will omit body if method is HEAD", async () => { - const mockResponse = { - headers: { entries: jest.fn().mockReturnValue([]) }, - blob: jest.fn().mockResolvedValue(new Blob()), - }; - const mockFetch = jest.fn().mockResolvedValue(mockResponse); - - (global as any).fetch = mockFetch; - - const httpRequest = new HttpRequest({ - headers: {}, - hostname: "foo.amazonaws.com", - method: "HEAD", - path: "/", - body: "will be omitted", - }); - const fetchHttpHandler = new FetchHttpHandler(); - - await fetchHttpHandler.handle(httpRequest, {}); - - expect(mockFetch.mock.calls.length).toBe(1); - const requestCall = mockRequest.mock.calls[0]; - expect(requestCall[1].body).toBeUndefined(); - }); - - it("will not make request if already aborted", async () => { - const mockResponse = { - headers: { - entries: jest.fn().mockReturnValue([ - ["foo", "bar"], - ["bizz", "bazz"], - ]), - }, - blob: jest.fn().mockResolvedValue(new Blob()), - }; - const mockFetch = jest.fn().mockResolvedValue(mockResponse); - - (global as any).fetch = mockFetch; - const fetchHttpHandler = new FetchHttpHandler(); - - await expect( - fetchHttpHandler.handle({} as any, { - abortSignal: { - aborted: true, - onabort: null, - }, - }) - ).rejects.toHaveProperty("name", "AbortError"); - - expect(mockFetch.mock.calls.length).toBe(0); - }); - - it("will pass abortSignal to fetch if supported", async () => { - const mockResponse = { - headers: { - entries: jest.fn().mockReturnValue([ - ["foo", "bar"], - ["bizz", "bazz"], - ]), - }, - blob: jest.fn().mockResolvedValue(new Blob()), - }; - const mockFetch = jest.fn().mockResolvedValue(mockResponse); - (global as any).fetch = mockFetch; - (global as any).AbortController = jest.fn(); - const fetchHttpHandler = new FetchHttpHandler(); - - await fetchHttpHandler.handle({} as any, { - abortSignal: { - aborted: false, - onabort: null, - }, - }); - - expect(mockRequest.mock.calls[0][1]).toHaveProperty("signal"); - expect(mockFetch.mock.calls.length).toBe(1); - }); - - it("will pass timeout to request timeout", async () => { - const mockResponse = { - headers: { - entries: jest.fn().mockReturnValue([ - ["foo", "bar"], - ["bizz", "bazz"], - ]), - }, - blob: jest.fn().mockResolvedValue(new Blob()), - }; - const mockFetch = jest.fn().mockResolvedValue(mockResponse); - (global as any).fetch = mockFetch; - - timeoutSpy = jest.spyOn({ requestTimeout }, "requestTimeout"); - const fetchHttpHandler = new FetchHttpHandler({ - requestTimeout: 500, - }); - - await fetchHttpHandler.handle({} as any, {}); - - expect(mockFetch.mock.calls.length).toBe(1); - }); - - it("will pass timeout from a provider to request timeout", async () => { - const mockResponse = { - headers: { - entries: () => [], - }, - blob: async () => new Blob(), - }; - const mockFetch = jest.fn().mockResolvedValue(mockResponse); - (global as any).fetch = mockFetch; - - timeoutSpy = jest.spyOn({ requestTimeout }, "requestTimeout"); - const fetchHttpHandler = new FetchHttpHandler(async () => ({ - requestTimeout: 500, - })); - - await fetchHttpHandler.handle({} as any, {}); - - expect(mockFetch.mock.calls.length).toBe(1); - }); - - it("will throw timeout error it timeout finishes before request", async () => { - const mockFetch = jest.fn(() => { - return new Promise(() => {}); - }); - (global as any).fetch = mockFetch; - const fetchHttpHandler = new FetchHttpHandler({ - requestTimeout: 5, - }); - - await expect(fetchHttpHandler.handle({} as any, {})).rejects.toHaveProperty("name", "TimeoutError"); - expect(mockFetch.mock.calls.length).toBe(1); - }); - - it("can be aborted before fetch completes", async () => { - const abortController = new AbortController(); - - const mockFetch = jest.fn(() => { - return new Promise(() => {}); - }); - (global as any).fetch = mockFetch; - - setTimeout(() => { - abortController.abort(); - }, 100); - const fetchHttpHandler = new FetchHttpHandler(); - - await expect( - fetchHttpHandler.handle({} as any, { - abortSignal: abortController.signal, - }) - ).rejects.toHaveProperty("name", "AbortError"); - - // ensure that fetch's built-in mechanism isn't being used - expect(mockRequest.mock.calls[0][1]).not.toHaveProperty("signal"); - }); - - it("creates correct HTTPResponse object", async () => { - const mockResponse = { - headers: { - entries: jest.fn().mockReturnValue([["foo", "bar"]]), - }, - blob: jest.fn().mockResolvedValue(new Blob(["FOO"])), - status: 200, - statusText: "foo", - }; - const mockFetch = jest.fn().mockResolvedValue(mockResponse); - (global as any).fetch = mockFetch; - - const fetchHttpHandler = new FetchHttpHandler(); - const { response } = await fetchHttpHandler.handle({} as any, {}); - - expect(mockFetch.mock.calls.length).toBe(1); - expect(response.headers).toStrictEqual({ foo: "bar" }); - expect(response.reason).toBe("foo"); - expect(response.statusCode).toBe(200); - expect(await blobToText(response.body)).toBe("FOO"); - }); - - describe("#destroy", () => { - it("should be callable and return nothing", () => { - const httpHandler = new FetchHttpHandler(); - expect(httpHandler.destroy()).toBeUndefined(); - }); - }); - - // The Blob implementation does not implement Blob.text, so we deal with it here. - async function blobToText(blob: Blob): Promise { - const reader = new FileReader(); - - return new Promise((resolve) => { - // This fires after the blob has been read/loaded. - reader.addEventListener("loadend", (e) => { - const text = e.target!.result as string; - resolve(text); - }); - - // Start reading the blob as text. - reader.readAsText(blob); - }); - } -}); diff --git a/packages/fetch-http-handler/src/fetch-http-handler.ts b/packages/fetch-http-handler/src/fetch-http-handler.ts deleted file mode 100644 index e8802a24f7e9..000000000000 --- a/packages/fetch-http-handler/src/fetch-http-handler.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { HttpHandler, HttpRequest, HttpResponse } from "@aws-sdk/protocol-http"; -import { buildQueryString } from "@aws-sdk/querystring-builder"; -import { HeaderBag, HttpHandlerOptions, Provider } from "@aws-sdk/types"; - -import { requestTimeout } from "./request-timeout"; - -declare let AbortController: any; - -/** - * Represents the http options that can be passed to a browser http client. - */ -export interface FetchHttpHandlerOptions { - /** - * The number of milliseconds a request can take before being automatically - * terminated. - */ - requestTimeout?: number; -} - -type FetchHttpHandlerConfig = FetchHttpHandlerOptions; - -export class FetchHttpHandler implements HttpHandler { - private config?: FetchHttpHandlerConfig; - private readonly configProvider: Promise; - - constructor(options?: FetchHttpHandlerOptions | Provider) { - if (typeof options === "function") { - this.configProvider = options().then((opts) => opts || {}); - } else { - this.config = options ?? {}; - this.configProvider = Promise.resolve(this.config); - } - } - - destroy(): void { - // Do nothing. TLS and HTTP/2 connection pooling is handled by the browser. - } - - async handle(request: HttpRequest, { abortSignal }: HttpHandlerOptions = {}): Promise<{ response: HttpResponse }> { - if (!this.config) { - this.config = await this.configProvider; - } - const requestTimeoutInMs = this.config!.requestTimeout; - - // if the request was already aborted, prevent doing extra work - if (abortSignal?.aborted) { - const abortError = new Error("Request aborted"); - abortError.name = "AbortError"; - return Promise.reject(abortError); - } - - let path = request.path; - const queryString = buildQueryString(request.query || {}); - if (queryString) { - path += `?${queryString}`; - } - if (request.fragment) { - path += `#${request.fragment}`; - } - - let auth = ""; - if (request.username != null || request.password != null) { - const username = request.username ?? ""; - const password = request.password ?? ""; - auth = `${username}:${password}@`; - } - - const { port, method } = request; - const url = `${request.protocol}//${auth}${request.hostname}${port ? `:${port}` : ""}${path}`; - // Request constructor doesn't allow GET/HEAD request with body - // ref: https://github.com/whatwg/fetch/issues/551 - const body = method === "GET" || method === "HEAD" ? undefined : request.body; - const requestOptions: RequestInit = { - body, - headers: new Headers(request.headers), - method: method, - }; - - // some browsers support abort signal - if (typeof AbortController !== "undefined") { - (requestOptions as any)["signal"] = abortSignal; - } - - const fetchRequest = new Request(url, requestOptions); - const raceOfPromises = [ - fetch(fetchRequest).then((response) => { - const fetchHeaders: any = response.headers; - const transformedHeaders: HeaderBag = {}; - - for (const pair of >fetchHeaders.entries()) { - transformedHeaders[pair[0]] = pair[1]; - } - - // Check for undefined as well as null. - const hasReadableStream = response.body != undefined; - - // Return the response with buffered body - if (!hasReadableStream) { - return response.blob().then((body) => ({ - response: new HttpResponse({ - headers: transformedHeaders, - reason: response.statusText, - statusCode: response.status, - body, - }), - })); - } - // Return the response with streaming body - return { - response: new HttpResponse({ - headers: transformedHeaders, - reason: response.statusText, - statusCode: response.status, - body: response.body, - }), - }; - }), - requestTimeout(requestTimeoutInMs), - ]; - if (abortSignal) { - raceOfPromises.push( - new Promise((resolve, reject) => { - abortSignal.onabort = () => { - const abortError = new Error("Request aborted"); - abortError.name = "AbortError"; - reject(abortError); - }; - }) - ); - } - return Promise.race(raceOfPromises); - } -} diff --git a/packages/fetch-http-handler/src/index.spec.ts b/packages/fetch-http-handler/src/index.spec.ts deleted file mode 100644 index f3f526fd13bd..000000000000 --- a/packages/fetch-http-handler/src/index.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { FetchHttpHandler } from "./index"; - -describe("index", () => { - it("exports FetchHttpHandler", () => { - expect(typeof FetchHttpHandler).toBe("function"); - }); -}); diff --git a/packages/fetch-http-handler/src/index.ts b/packages/fetch-http-handler/src/index.ts index a0c61f1b0840..a52aa17d99f3 100644 --- a/packages/fetch-http-handler/src/index.ts +++ b/packages/fetch-http-handler/src/index.ts @@ -1,2 +1 @@ -export * from "./fetch-http-handler"; -export * from "./stream-collector"; +export * from "@smithy/fetch-http-handler"; diff --git a/packages/fetch-http-handler/src/request-timeout.ts b/packages/fetch-http-handler/src/request-timeout.ts deleted file mode 100644 index d6132d119478..000000000000 --- a/packages/fetch-http-handler/src/request-timeout.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function requestTimeout(timeoutInMs = 0): Promise { - return new Promise((resolve, reject) => { - if (timeoutInMs) { - setTimeout(() => { - const timeoutError = new Error(`Request did not complete within ${timeoutInMs} ms`); - timeoutError.name = "TimeoutError"; - reject(timeoutError); - }, timeoutInMs); - } - }); -} diff --git a/packages/fetch-http-handler/src/stream-collector.browser.spec.ts b/packages/fetch-http-handler/src/stream-collector.browser.spec.ts deleted file mode 100644 index c8ded648ca02..000000000000 --- a/packages/fetch-http-handler/src/stream-collector.browser.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { streamCollector } from "./stream-collector"; - -describe("streamCollector", () => { - it("returns a Uint8Array from a blob", (done) => { - const expected = Uint8Array.from([102, 111, 111]); - const dataPromise = new Response(expected.buffer).blob().then((blob) => streamCollector(blob)); - dataPromise.then((data: any) => { - expect(data).toEqual(expected); - done(); - }); - }); - - it("returns a Uint8Array from a ReadableStream", (done) => { - const expected = Uint8Array.from([102, 111, 111]); - const dataPromise = streamCollector(new Response(expected.buffer).body); - dataPromise.then((data: any) => { - expect(data).toEqual(expected); - done(); - }); - }); - - it("returns a Uint8Array when stream is empty", (done) => { - const expected = new Uint8Array(0); - const dataPromise = streamCollector(new Response(expected.buffer).body); - dataPromise.then((data: any) => { - expect(data).toEqual(expected); - done(); - }); - }); - - it("returns a Uint8Array when blob is empty", (done) => { - const expected = new Uint8Array(0); - - const dataPromise = new Response(expected.buffer).blob().then((blob) => streamCollector(blob)); - dataPromise.then((data: any) => { - expect(data).toEqual(expected); - done(); - }); - }); -}); diff --git a/packages/fetch-http-handler/src/stream-collector.spec.ts b/packages/fetch-http-handler/src/stream-collector.spec.ts deleted file mode 100644 index 7f7e47e9ffcb..000000000000 --- a/packages/fetch-http-handler/src/stream-collector.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { streamCollector } from "./stream-collector"; - -/** - * Have to mock the FileReader behavior in IE, where - * reader.result is null if reads an empty blob. - */ -describe("streamCollector", () => { - let originalFileReader = (global as any).FileReader; - let originalBlob = (global as any).Blob; - beforeAll(() => { - originalFileReader = (global as any).FileReader; - originalBlob = (global as any).Blob; - }); - afterAll(() => { - (global as any).FileReader = originalFileReader; - (global as any).Blob = originalBlob; - }); - - it("returns a Uint8Array when blob is empty and when FileReader data is null(in IE)", (done) => { - (global as any).FileReader = function FileReader() { - this.result = null; //In IE, FileReader.result is null after reading empty blob - this.readAsDataURL = jest.fn().mockImplementation(() => { - if (this.onloadend) { - this.readyState = 2; - this.onloadend(); - } - }); - }; - (global as any).Blob = function Blob() {}; - const dataPromise = streamCollector(new Blob()); - dataPromise.then((data: any) => { - expect(data).toEqual(Uint8Array.from([])); - done(); - }); - }); -}); diff --git a/packages/fetch-http-handler/src/stream-collector.ts b/packages/fetch-http-handler/src/stream-collector.ts deleted file mode 100644 index e1dd0b6be750..000000000000 --- a/packages/fetch-http-handler/src/stream-collector.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { StreamCollector } from "@aws-sdk/types"; -import { fromBase64 } from "@aws-sdk/util-base64"; - -//reference: https://snack.expo.io/r1JCSWRGU -export const streamCollector: StreamCollector = (stream: Blob | ReadableStream): Promise => { - if (typeof Blob === "function" && stream instanceof Blob) { - return collectBlob(stream); - } - - return collectStream(stream as ReadableStream); -}; - -async function collectBlob(blob: Blob): Promise { - const base64 = await readToBase64(blob); - const arrayBuffer = fromBase64(base64); - return new Uint8Array(arrayBuffer); -} - -async function collectStream(stream: ReadableStream): Promise { - let res = new Uint8Array(0); - const reader = stream.getReader(); - let isDone = false; - while (!isDone) { - const { done, value } = await reader.read(); - if (value) { - const prior = res; - res = new Uint8Array(prior.length + value.length); - res.set(prior); - res.set(value, prior.length); - } - isDone = done; - } - return res; -} - -function readToBase64(blob: Blob): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onloadend = () => { - // reference: https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL - // response from readAsDataURL is always prepended with "data:*/*;base64," - if (reader.readyState !== 2) { - return reject(new Error("Reader aborted too early")); - } - const result = (reader.result ?? "") as string; - // Response can include only 'data:' for empty blob, return empty string in this case. - // Otherwise, return the string after ',' - const commaIndex = result.indexOf(","); - const dataOffset = commaIndex > -1 ? commaIndex + 1 : result.length; - resolve(result.substring(dataOffset)); - }; - reader.onabort = () => reject(new Error("Read aborted")); - reader.onerror = () => reject(reader.error); - // reader.readAsArrayBuffer is not always available - reader.readAsDataURL(blob); - }); -} diff --git a/packages/hash-blob-browser/jest.config.js b/packages/hash-blob-browser/jest.config.js deleted file mode 100644 index bd895a5df03e..000000000000 --- a/packages/hash-blob-browser/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, - testEnvironment: "jsdom", -}; diff --git a/packages/hash-blob-browser/karma.conf.js b/packages/hash-blob-browser/karma.conf.js deleted file mode 100644 index 1ef247459a0d..000000000000 --- a/packages/hash-blob-browser/karma.conf.js +++ /dev/null @@ -1,26 +0,0 @@ -// Set up binary for Chromium browser in CHROME_BIN environment variable before running the test - -module.exports = function (config) { - config.set({ - frameworks: ["jasmine", "karma-typescript"], - files: ["src/**/*.ts"], - exclude: ["**/*.d.ts"], - preprocessors: { - "**/*.ts": "karma-typescript", - }, - reporters: ["progress", "karma-typescript"], - browsers: ["ChromeHeadlessNoSandbox"], - customLaunchers: { - ChromeHeadlessNoSandbox: { - base: "ChromeHeadless", - flags: ["--no-sandbox"], - }, - }, - karmaTypescriptConfig: { - bundlerOptions: { - addNodeGlobals: false, - }, - }, - singleRun: true, - }); -}; diff --git a/packages/hash-blob-browser/package.json b/packages/hash-blob-browser/package.json index 4ae702a2b5da..4542e094bb4f 100644 --- a/packages/hash-blob-browser/package.json +++ b/packages/hash-blob-browser/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "karma start karma.conf.js" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,9 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/chunked-blob-reader": "*", - "@aws-sdk/chunked-blob-reader-native": "*", - "@aws-sdk/types": "*", + "@smithy/hash-blob-browser": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/hash-blob-browser/src/index.spec.ts b/packages/hash-blob-browser/src/index.spec.ts deleted file mode 100644 index 850f3712c44f..000000000000 --- a/packages/hash-blob-browser/src/index.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Sha256 } from "@aws-crypto/sha256-js"; -import { toHex } from "@aws-sdk/util-hex-encoding"; - -import { blobHasher } from "./index"; - -describe("blobHasher", () => { - const blob = new Blob(["Shot through the bar, but you're too late bizzbuzz you give foo, a bad name."]); - - it("calculates the SHA256 hash of a blob", async () => { - const result = await blobHasher(Sha256, blob); - - expect(result instanceof Uint8Array).toBe(true); - expect(toHex(result)).toBe("24dabf4db3774a3224d571d4c089a9c570c3045dbe1e67ee9ee2e2677f57dbe0"); - }); -}); diff --git a/packages/hash-blob-browser/src/index.ts b/packages/hash-blob-browser/src/index.ts index fcc95721dc52..08b1b26e1a5e 100644 --- a/packages/hash-blob-browser/src/index.ts +++ b/packages/hash-blob-browser/src/index.ts @@ -1,18 +1 @@ -import { blobReader } from "@aws-sdk/chunked-blob-reader"; -import { ChecksumConstructor, HashConstructor, StreamHasher } from "@aws-sdk/types"; - -/** - * @internal - */ -export const blobHasher: StreamHasher = async function blobHasher( - hashCtor: ChecksumConstructor | HashConstructor, - blob: Blob -): Promise { - const hash = new hashCtor(); - - await blobReader(blob, (chunk) => { - hash.update(chunk); - }); - - return hash.digest(); -}; +export * from "@smithy/hash-blob-browser"; diff --git a/packages/hash-node/jest.config.js b/packages/hash-node/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/hash-node/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/hash-node/package.json b/packages/hash-node/package.json index b7e1a691e9bd..66455114f0c0 100644 --- a/packages/hash-node/package.json +++ b/packages/hash-node/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -30,9 +30,7 @@ "typescript": "~4.9.5" }, "dependencies": { - "@aws-sdk/types": "*", - "@aws-sdk/util-buffer-from": "*", - "@aws-sdk/util-utf8": "*", + "@smithy/hash-node": "^1.0.1", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/hash-node/src/index.spec.ts b/packages/hash-node/src/index.spec.ts deleted file mode 100644 index 898ba4fd8bb0..000000000000 --- a/packages/hash-node/src/index.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { fromArrayBuffer, fromString } from "@aws-sdk/util-buffer-from"; - -import { Hash } from "./"; -const hashVectors = require("hash-test-vectors"); -const hmacVectors = require("hash-test-vectors/hmac"); - -describe("Hash", () => { - for (const supportedHash of ["md5", "sha256"]) { - describe(`${supportedHash} test vectors`, () => { - for (const { input, ...results } of hashVectors) { - const expected = results[supportedHash]; - it(`should calculate a ${supportedHash} hash of ${expected} for an input of ${input}`, async () => { - const hash = new Hash(supportedHash); - hash.update(fromString(input, "base64")); - const { buffer } = await hash.digest(); - expect(fromArrayBuffer(buffer).toString("hex")).toBe(expected); - }); - } - - for (const { key, data, ...results } of hmacVectors) { - const expected = results[supportedHash]; - it(`should calculate a ${supportedHash} hmac of ${expected} for an input of ${data} and a key of ${key}`, async () => { - const hash = new Hash(supportedHash, fromString(key, "hex")); - hash.update(fromString(data, "hex")); - const { buffer } = await hash.digest(); - expect(fromArrayBuffer(buffer).toString("hex")).toMatch(expected); - }); - } - }); - } - - it("should accept string data", async () => { - const hash = new Hash("md5"); - hash.update(""); - const { buffer } = await hash.digest(); - expect(fromArrayBuffer(buffer).toString("hex")).toBe("d41d8cd98f00b204e9800998ecf8427e"); - }); - - it("should accept ArrayBuffer data", async () => { - const hash = new Hash("md5"); - hash.update(new ArrayBuffer(0)); - const { buffer } = await hash.digest(); - expect(fromArrayBuffer(buffer).toString("hex")).toBe("d41d8cd98f00b204e9800998ecf8427e"); - }); - - it("should accept ArrayBufferView data", async () => { - const hash = new Hash("md5"); - hash.update(new Uint8Array(0)); - const { buffer } = await hash.digest(); - expect(fromArrayBuffer(buffer).toString("hex")).toBe("d41d8cd98f00b204e9800998ecf8427e"); - }); -}); diff --git a/packages/hash-node/src/index.ts b/packages/hash-node/src/index.ts index ef1819e4be2d..46d1e9eb8d2f 100644 --- a/packages/hash-node/src/index.ts +++ b/packages/hash-node/src/index.ts @@ -1,50 +1 @@ -import { Checksum, SourceData } from "@aws-sdk/types"; -import { fromArrayBuffer, fromString, StringEncoding } from "@aws-sdk/util-buffer-from"; -import { toUint8Array } from "@aws-sdk/util-utf8"; -import { Buffer } from "buffer"; -import { createHash, createHmac, Hash as NodeHash, Hmac } from "crypto"; - -/** - * @internal - */ -export class Hash implements Checksum { - private readonly algorithmIdentifier: string; - private readonly secret?: SourceData; - private hash!: NodeHash | Hmac; - - constructor(algorithmIdentifier: string, secret?: SourceData) { - this.algorithmIdentifier = algorithmIdentifier; - this.secret = secret; - this.reset(); - } - - update(toHash: SourceData, encoding?: "utf8" | "ascii" | "latin1"): void { - this.hash.update(toUint8Array(castSourceData(toHash, encoding))); - } - - digest(): Promise { - return Promise.resolve(this.hash.digest()); - } - - reset(): void { - this.hash = this.secret - ? createHmac(this.algorithmIdentifier, castSourceData(this.secret)) - : createHash(this.algorithmIdentifier); - } -} - -function castSourceData(toCast: SourceData, encoding?: StringEncoding): Buffer { - if (Buffer.isBuffer(toCast)) { - return toCast; - } - - if (typeof toCast === "string") { - return fromString(toCast, encoding); - } - - if (ArrayBuffer.isView(toCast)) { - return fromArrayBuffer(toCast.buffer, toCast.byteOffset, toCast.byteLength); - } - - return fromArrayBuffer(toCast); -} +export * from "@smithy/hash-node"; diff --git a/packages/hash-stream-node/jest.config.js b/packages/hash-stream-node/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/hash-stream-node/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/hash-stream-node/package.json b/packages/hash-stream-node/package.json index fe1567b3fc07..ca0fce6c1c58 100644 --- a/packages/hash-stream-node/package.json +++ b/packages/hash-stream-node/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,8 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "*", - "@aws-sdk/util-utf8": "*", + "@smithy/hash-stream-node": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/hash-stream-node/src/HashCalculator.spec.ts b/packages/hash-stream-node/src/HashCalculator.spec.ts deleted file mode 100644 index 98016c791a16..000000000000 --- a/packages/hash-stream-node/src/HashCalculator.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { toUint8Array } from "@aws-sdk/util-utf8/src"; - -import { HashCalculator } from "./HashCalculator"; - -function createMockHash(): { - updates: Uint8Array[]; - update: (data: Uint8Array) => void; - digest: () => Promise; - reset: () => void; -} { - const mockHash: any = { - updates: [] as Uint8Array[], - }; - mockHash.update = (data: Uint8Array) => { - mockHash.updates.push(data); - }; - mockHash.digest = async () => { - return Uint8Array.from([102, 111, 111]); // foo - }; - mockHash.reset = () => {}; - return mockHash; -} - -describe("HashCalculator", () => { - const writePromise = ( - calculator: HashCalculator, - chunk: Buffer, - encoding: BufferEncoding = "utf-8" - ): Promise => { - return new Promise((resolve, reject) => { - calculator.write(chunk, encoding, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - }; - - const listOfBuffers: Buffer[] = [Buffer.from("foo"), Buffer.from("bar"), Buffer.from("buzz")]; - - it("updates a hash from upstream stream", async () => { - const mockHash = createMockHash(); - const calculator = new HashCalculator(mockHash); - - await writePromise(calculator, listOfBuffers[0]); - await writePromise(calculator, listOfBuffers[1]); - await writePromise(calculator, listOfBuffers[2]); - calculator.end(); - - // verify that update was called the correct number of times - expect(mockHash.updates.length).toBe(3); - expect(mockHash.updates[0]).toEqual(toUint8Array(listOfBuffers[0])); - expect(mockHash.updates[1]).toEqual(toUint8Array(listOfBuffers[1])); - expect(mockHash.updates[2]).toEqual(toUint8Array(listOfBuffers[2])); - }); -}); diff --git a/packages/hash-stream-node/src/HashCalculator.ts b/packages/hash-stream-node/src/HashCalculator.ts deleted file mode 100644 index ac0de8e73d8b..000000000000 --- a/packages/hash-stream-node/src/HashCalculator.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Checksum, Hash } from "@aws-sdk/types"; -import { toUint8Array } from "@aws-sdk/util-utf8"; -import { Writable, WritableOptions } from "stream"; - -/** - * @internal - */ -export class HashCalculator extends Writable { - constructor(public readonly hash: Checksum | Hash, options?: WritableOptions) { - super(options); - } - - _write(chunk: Buffer, encoding: string, callback: (err?: Error) => void) { - try { - this.hash.update(toUint8Array(chunk)); - } catch (err) { - return callback(err); - } - callback(); - } -} diff --git a/packages/hash-stream-node/src/fileStreamHasher.spec.ts b/packages/hash-stream-node/src/fileStreamHasher.spec.ts deleted file mode 100644 index 4eaa73e180a9..000000000000 --- a/packages/hash-stream-node/src/fileStreamHasher.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Sha256 } from "@aws-crypto/sha256-js"; -import { toHex } from "@aws-sdk/util-hex-encoding"; -import { createReadStream, mkdtempSync, writeFileSync } from "fs"; -import { tmpdir } from "os"; -import { join } from "path"; -import { Readable } from "stream"; - -import { fileStreamHasher } from "./fileStreamHasher"; - -function createTemporaryFile(contents: string): string { - const folder = mkdtempSync(join(tmpdir(), "sha256-stream-node-")); - const fileLoc = join(folder, "test.txt"); - writeFileSync(fileLoc, contents); - - return fileLoc; -} - -describe("fileStreamHasher", () => { - const temporaryFile = createTemporaryFile( - "Shot through the bar, but you're too late bizzbuzz you give foo, a bad name." - ); - - it("calculates the SHA256 hash of a stream", async () => { - const result = await fileStreamHasher(Sha256, createReadStream(temporaryFile)); - - expect(result instanceof Uint8Array).toBe(true); - expect(toHex(result)).toBe("24dabf4db3774a3224d571d4c089a9c570c3045dbe1e67ee9ee2e2677f57dbe0"); - }); - - it("does not exhaust the input stream", async () => { - const inputStream = createReadStream(temporaryFile); - - const onSpy = jest.spyOn(inputStream, "on"); - const pipeSpy = jest.spyOn(inputStream, "pipe"); - - const result = await fileStreamHasher(Sha256, inputStream); - - expect(result instanceof Uint8Array).toBe(true); - expect(toHex(result)).toBe("24dabf4db3774a3224d571d4c089a9c570c3045dbe1e67ee9ee2e2677f57dbe0"); - expect(onSpy.mock.calls.length).toBe(0); - expect(pipeSpy.mock.calls.length).toBe(0); - }); - - it("throws an error when a non-file stream is encountered", async () => { - const inputStream = new Readable(); - - await expect(fileStreamHasher(Sha256, inputStream as any)).rejects.toHaveProperty("message"); - }); -}); diff --git a/packages/hash-stream-node/src/fileStreamHasher.ts b/packages/hash-stream-node/src/fileStreamHasher.ts deleted file mode 100644 index 636b3d60479d..000000000000 --- a/packages/hash-stream-node/src/fileStreamHasher.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { HashConstructor, StreamHasher } from "@aws-sdk/types"; -import { createReadStream, ReadStream } from "fs"; -import { Readable } from "stream"; - -import { HashCalculator } from "./HashCalculator"; - -// ToDo: deprecate in favor of readableStreamHasher -/** - * @internal - */ -export const fileStreamHasher: StreamHasher = (hashCtor: HashConstructor, fileStream: Readable) => - new Promise((resolve, reject) => { - if (!isReadStream(fileStream)) { - reject(new Error("Unable to calculate hash for non-file streams.")); - return; - } - - const fileStreamTee = createReadStream(fileStream.path, { - start: (fileStream as any).start, - end: (fileStream as any).end, - }); - - const hash = new hashCtor(); - const hashCalculator = new HashCalculator(hash); - - fileStreamTee.pipe(hashCalculator); - fileStreamTee.on("error", (err: any) => { - // if the source errors, the destination stream needs to manually end - hashCalculator.end(); - reject(err); - }); - hashCalculator.on("error", reject); - hashCalculator.on("finish", function (this: HashCalculator) { - hash.digest().then(resolve).catch(reject); - }); - }); - -const isReadStream = (stream: Readable): stream is ReadStream => typeof (stream as ReadStream).path === "string"; diff --git a/packages/hash-stream-node/src/index.ts b/packages/hash-stream-node/src/index.ts index 391fecd54bcb..66cf15fbbe2d 100644 --- a/packages/hash-stream-node/src/index.ts +++ b/packages/hash-stream-node/src/index.ts @@ -1,8 +1 @@ -/** - * @internal - */ -export * from "./fileStreamHasher"; -/** - * @internal - */ -export * from "./readableStreamHasher"; +export * from "@smithy/hash-stream-node"; diff --git a/packages/hash-stream-node/src/readableStreamHasher.spec.ts b/packages/hash-stream-node/src/readableStreamHasher.spec.ts deleted file mode 100644 index 84eca855e94c..000000000000 --- a/packages/hash-stream-node/src/readableStreamHasher.spec.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { Hash } from "@aws-sdk/types"; -import { Readable, Writable } from "stream"; - -import { HashCalculator } from "./HashCalculator"; -import { readableStreamHasher } from "./readableStreamHasher"; - -jest.mock("./HashCalculator"); - -describe(readableStreamHasher.name, () => { - const mockDigest = jest.fn(); - const mockHashCtor = jest.fn().mockImplementation(() => ({ - update: jest.fn(), - digest: mockDigest, - })); - - const mockHashCalculatorWrite = jest.fn(); - const mockHashCalculatorEnd = jest.fn(); - - const mockHash = new Uint8Array(Buffer.from("mockHash")); - - class MockHashCalculator extends Writable { - constructor(public readonly hash: Hash, public readonly mockWrite, public readonly mockEnd) { - super(); - } - - _write(chunk: Buffer, encoding: string, callback: (err?: Error) => void) { - this.mockWrite(chunk); - callback(); - } - - end() { - this.mockEnd(); - return super.end(); - } - } - - beforeEach(() => { - (HashCalculator as unknown as jest.Mock).mockImplementation( - (hash) => new MockHashCalculator(hash, mockHashCalculatorWrite, mockHashCalculatorEnd) - ); - mockDigest.mockResolvedValue(mockHash); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("computes hash for a readable stream", async () => { - const readableStream = new Readable({ read: (size) => {} }); - const hashPromise = readableStreamHasher(mockHashCtor, readableStream); - - // @ts-ignore Property '_readableState' does not exist on type 'Readable'. - const { pipesCount } = readableStream._readableState; - expect(pipesCount).toEqual(1); - - const mockDataChunks = ["Hello", "World"]; - setTimeout(() => { - mockDataChunks.forEach((chunk) => readableStream.emit("data", chunk)); - readableStream.emit("end"); - }, 100); - - expect(await hashPromise).toEqual(mockHash); - expect(mockHashCalculatorWrite).toHaveBeenCalledTimes(mockDataChunks.length); - mockDataChunks.forEach((chunk, index) => - expect(mockHashCalculatorWrite).toHaveBeenNthCalledWith(index + 1, Buffer.from(chunk)) - ); - expect(mockDigest).toHaveBeenCalledTimes(1); - expect(mockHashCalculatorEnd).toHaveBeenCalledTimes(1); - }); - - it("throws if readable stream has started reading", async () => { - const readableStream = new Readable({ read: (size) => {} }); - // Simulate readableFlowing to true. - readableStream.resume(); - - const expectedError = new Error("Unable to calculate hash for flowing readable stream"); - try { - readableStreamHasher(mockHashCtor, readableStream); - fail(`expected ${expectedError}`); - } catch (error) { - expect(error).toStrictEqual(expectedError); - } - }); - - it("throws error if readable stream throws error", async () => { - const readableStream = new Readable({ - read: (size) => {}, - }); - const hashPromise = readableStreamHasher(mockHashCtor, readableStream); - - const mockError = new Error("error"); - setTimeout(() => { - readableStream.emit("error", mockError); - }, 100); - - try { - await hashPromise; - fail(`should throw error ${mockError}`); - } catch (error) { - expect(error).toEqual(mockError); - expect(mockHashCalculatorEnd).toHaveBeenCalledTimes(1); - } - }); - - it("throws error if HashCalculator throws error", async () => { - const mockHashCalculator = new MockHashCalculator( - mockHashCtor as any, - mockHashCalculatorWrite, - mockHashCalculatorEnd - ); - (HashCalculator as unknown as jest.Mock).mockImplementation((hash) => mockHashCalculator); - - const readableStream = new Readable({ - read: (size) => {}, - }); - const hashPromise = readableStreamHasher(mockHashCtor, readableStream); - - const mockError = new Error("error"); - setTimeout(() => { - mockHashCalculator.emit("error", mockError); - }, 100); - - try { - await hashPromise; - fail(`should throw error ${mockError}`); - } catch (error) { - expect(error).toEqual(mockError); - } - }); - - it("throws error if hash.digest() throws error", async () => { - const readableStream = new Readable({ - read: (size) => {}, - }); - const hashPromise = readableStreamHasher(mockHashCtor, readableStream); - - setTimeout(() => { - readableStream.emit("end"); - }, 100); - - const mockError = new Error("error"); - mockDigest.mockRejectedValue(mockError); - - try { - await hashPromise; - fail(`should throw error ${mockError}`); - } catch (error) { - expect(error).toEqual(mockError); - expect(mockHashCalculatorEnd).toHaveBeenCalledTimes(1); - } - }); -}); diff --git a/packages/hash-stream-node/src/readableStreamHasher.ts b/packages/hash-stream-node/src/readableStreamHasher.ts deleted file mode 100644 index 1a74e6160559..000000000000 --- a/packages/hash-stream-node/src/readableStreamHasher.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { HashConstructor, StreamHasher } from "@aws-sdk/types"; -import { Readable } from "stream"; - -import { HashCalculator } from "./HashCalculator"; - -/** - * @internal - */ -export const readableStreamHasher: StreamHasher = (hashCtor: HashConstructor, readableStream: Readable) => { - // Throw if readableStream is already flowing. - if (readableStream.readableFlowing !== null) { - throw new Error("Unable to calculate hash for flowing readable stream"); - } - - const hash = new hashCtor(); - const hashCalculator = new HashCalculator(hash); - readableStream.pipe(hashCalculator); - - return new Promise((resolve, reject) => { - readableStream.on("error", (err: Error) => { - // if the source errors, the destination stream needs to manually end - hashCalculator.end(); - reject(err); - }); - hashCalculator.on("error", reject); - hashCalculator.on("finish", () => { - hash.digest().then(resolve).catch(reject); - }); - }); -}; diff --git a/packages/invalid-dependency/jest.config.js b/packages/invalid-dependency/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/invalid-dependency/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/invalid-dependency/package.json b/packages/invalid-dependency/package.json index 2d2cdaeb7899..3bdb8e8bbcc6 100644 --- a/packages/invalid-dependency/package.json +++ b/packages/invalid-dependency/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,7 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "*", + "@smithy/invalid-dependency": "^1.0.1", "tslib": "^2.5.0" }, "typesVersions": { diff --git a/packages/invalid-dependency/src/index.ts b/packages/invalid-dependency/src/index.ts index 1c99a5680fc4..f58635a64024 100644 --- a/packages/invalid-dependency/src/index.ts +++ b/packages/invalid-dependency/src/index.ts @@ -1,8 +1 @@ -/** - * @internal - */ -export * from "./invalidFunction"; -/** - * @internal - */ -export * from "./invalidProvider"; +export * from "@smithy/invalid-dependency"; diff --git a/packages/invalid-dependency/src/invalidFunction.spec.ts b/packages/invalid-dependency/src/invalidFunction.spec.ts deleted file mode 100644 index d58782e79276..000000000000 --- a/packages/invalid-dependency/src/invalidFunction.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { invalidFunction } from "./invalidFunction"; - -describe("invalidFunction", () => { - it("throws error with message", () => { - const message = "Error"; - expect(invalidFunction(message)).toThrowError(new Error(message)); - }); -}); diff --git a/packages/invalid-dependency/src/invalidFunction.ts b/packages/invalid-dependency/src/invalidFunction.ts deleted file mode 100644 index 07114cca8d1a..000000000000 --- a/packages/invalid-dependency/src/invalidFunction.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @internal - */ -export const invalidFunction = (message: string) => () => { - throw new Error(message); -}; diff --git a/packages/invalid-dependency/src/invalidProvider.spec.ts b/packages/invalid-dependency/src/invalidProvider.spec.ts deleted file mode 100644 index 4b7d01a6bc16..000000000000 --- a/packages/invalid-dependency/src/invalidProvider.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { invalidProvider } from "./invalidProvider"; - -describe("invalidProvider", () => { - it("rejects with error containing message", async () => { - const message = "Error"; - const provider = invalidProvider(message); - //@ts-ignore - await expect(provider()).rejects.toEqual(message); - }); -}); diff --git a/packages/invalid-dependency/src/invalidProvider.ts b/packages/invalid-dependency/src/invalidProvider.ts deleted file mode 100644 index c5b664962930..000000000000 --- a/packages/invalid-dependency/src/invalidProvider.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Provider } from "@aws-sdk/types"; -/** - * @internal - */ -export const invalidProvider: (message: string) => Provider = (message: string) => () => Promise.reject(message); diff --git a/packages/is-array-buffer/jest.config.js b/packages/is-array-buffer/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/is-array-buffer/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/is-array-buffer/package.json b/packages/is-array-buffer/package.json index 56a9d7982fbd..0976a94a2d42 100644 --- a/packages/is-array-buffer/package.json +++ b/packages/is-array-buffer/package.json @@ -10,7 +10,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", @@ -21,6 +21,7 @@ "module": "./dist-es/index.js", "types": "./dist-types/index.d.ts", "dependencies": { + "@smithy/is-array-buffer": "^1.0.1", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/is-array-buffer/src/index.spec.ts b/packages/is-array-buffer/src/index.spec.ts deleted file mode 100644 index 97986abed0e6..000000000000 --- a/packages/is-array-buffer/src/index.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { isArrayBuffer } from "./"; - -describe("isArrayBuffer", () => { - const arrayBufferConstructor = ArrayBuffer; - - afterEach(() => { - (ArrayBuffer as any) = arrayBufferConstructor; - }); - - it("should return true for ArrayBuffer objects", () => { - expect(isArrayBuffer(new ArrayBuffer(0))).toBe(true); - }); - - it("should return false for ArrayBufferView objects", () => { - const view = new Uint8Array(0); - - expect(isArrayBuffer(view)).toBe(false); - expect(isArrayBuffer(view.buffer)).toBe(true); - }); - - it("should return false for scalar values", () => { - for (const scalar of ["string", 123.234, true, null, void 0]) { - expect(isArrayBuffer(scalar)).toBe(false); - } - }); - - it("should return true for ArrayBuffers created with a different instance of the ArrayBuffer constructor", () => { - const buffer = new ArrayBuffer(0); - (ArrayBuffer as any) = jest.fn(() => buffer); - - expect(buffer).not.toBeInstanceOf(ArrayBuffer); - expect(isArrayBuffer(buffer)).toBe(true); - }); -}); diff --git a/packages/is-array-buffer/src/index.ts b/packages/is-array-buffer/src/index.ts index c972ff616fd4..ada1ebed371f 100644 --- a/packages/is-array-buffer/src/index.ts +++ b/packages/is-array-buffer/src/index.ts @@ -1,6 +1 @@ -/** - * @internal - */ -export const isArrayBuffer = (arg: any): arg is ArrayBuffer => - (typeof ArrayBuffer === "function" && arg instanceof ArrayBuffer) || - Object.prototype.toString.call(arg) === "[object ArrayBuffer]"; +export * from "@smithy/is-array-buffer"; diff --git a/packages/md5-js/jest.config.js b/packages/md5-js/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/md5-js/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/md5-js/package.json b/packages/md5-js/package.json index 44fa4c7e21ce..54abb5dbc605 100644 --- a/packages/md5-js/package.json +++ b/packages/md5-js/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -32,8 +32,7 @@ "typescript": "~4.9.5" }, "dependencies": { - "@aws-sdk/types": "*", - "@aws-sdk/util-utf8": "*", + "@smithy/md5-js": "^1.0.1", "tslib": "^2.5.0" }, "typesVersions": { diff --git a/packages/md5-js/src/constants.ts b/packages/md5-js/src/constants.ts deleted file mode 100644 index 08b33bf4c93d..000000000000 --- a/packages/md5-js/src/constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @internal - */ -export const BLOCK_SIZE = 64; - -/** - * @internal - */ -export const DIGEST_LENGTH = 16; - -/** - * @internal - */ -export const INIT = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]; diff --git a/packages/md5-js/src/index.spec.ts b/packages/md5-js/src/index.spec.ts deleted file mode 100644 index 755d4859cbbc..000000000000 --- a/packages/md5-js/src/index.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { fromBase64 } from "@aws-sdk/util-base64"; -import { toHex } from "@aws-sdk/util-hex-encoding"; - -import { Md5 } from "./"; -const hashVectors = require("hash-test-vectors"); - -describe("Md5", () => { - let idx = 0; - for (const { input, ...results } of hashVectors) { - const expected = results["md5"]; - it(`should calculate a MD5 hash of ${expected} for test vector ${++idx}`, async () => { - const hash = new Md5(); - hash.update(fromBase64(input)); - expect(toHex(await hash.digest())).toBe(expected); - }); - } -}); diff --git a/packages/md5-js/src/index.ts b/packages/md5-js/src/index.ts index 694c6a781bf9..c2a2290b9d19 100644 --- a/packages/md5-js/src/index.ts +++ b/packages/md5-js/src/index.ts @@ -1,208 +1 @@ -import { Checksum, SourceData } from "@aws-sdk/types"; -import { fromUtf8 } from "@aws-sdk/util-utf8"; - -import { BLOCK_SIZE, DIGEST_LENGTH, INIT } from "./constants"; - -/** - * @internal - */ -export class Md5 implements Checksum { - private state!: Uint32Array; - private buffer!: DataView; - private bufferLength!: number; - private bytesHashed!: number; - private finished!: boolean; - - constructor() { - this.reset(); - } - - update(sourceData: SourceData): void { - if (isEmptyData(sourceData)) { - return; - } else if (this.finished) { - throw new Error("Attempted to update an already finished hash."); - } - - const data = convertToBuffer(sourceData); - - let position = 0; - let { byteLength } = data; - this.bytesHashed += byteLength; - - while (byteLength > 0) { - this.buffer.setUint8(this.bufferLength++, data[position++]); - byteLength--; - - if (this.bufferLength === BLOCK_SIZE) { - this.hashBuffer(); - this.bufferLength = 0; - } - } - } - - async digest(): Promise { - if (!this.finished) { - const { buffer, bufferLength: undecoratedLength, bytesHashed } = this; - const bitsHashed = bytesHashed * 8; - buffer.setUint8(this.bufferLength++, 0b10000000); - - // Ensure the final block has enough room for the hashed length - if (undecoratedLength % BLOCK_SIZE >= BLOCK_SIZE - 8) { - for (let i = this.bufferLength; i < BLOCK_SIZE; i++) { - buffer.setUint8(i, 0); - } - this.hashBuffer(); - this.bufferLength = 0; - } - - for (let i = this.bufferLength; i < BLOCK_SIZE - 8; i++) { - buffer.setUint8(i, 0); - } - buffer.setUint32(BLOCK_SIZE - 8, bitsHashed >>> 0, true); - buffer.setUint32(BLOCK_SIZE - 4, Math.floor(bitsHashed / 0x100000000), true); - - this.hashBuffer(); - - this.finished = true; - } - - const out = new DataView(new ArrayBuffer(DIGEST_LENGTH)); - for (let i = 0; i < 4; i++) { - out.setUint32(i * 4, this.state[i], true); - } - - return new Uint8Array(out.buffer, out.byteOffset, out.byteLength); - } - - private hashBuffer(): void { - const { buffer, state } = this; - - let a = state[0], - b = state[1], - c = state[2], - d = state[3]; - - a = ff(a, b, c, d, buffer.getUint32(0, true), 7, 0xd76aa478); - d = ff(d, a, b, c, buffer.getUint32(4, true), 12, 0xe8c7b756); - c = ff(c, d, a, b, buffer.getUint32(8, true), 17, 0x242070db); - b = ff(b, c, d, a, buffer.getUint32(12, true), 22, 0xc1bdceee); - a = ff(a, b, c, d, buffer.getUint32(16, true), 7, 0xf57c0faf); - d = ff(d, a, b, c, buffer.getUint32(20, true), 12, 0x4787c62a); - c = ff(c, d, a, b, buffer.getUint32(24, true), 17, 0xa8304613); - b = ff(b, c, d, a, buffer.getUint32(28, true), 22, 0xfd469501); - a = ff(a, b, c, d, buffer.getUint32(32, true), 7, 0x698098d8); - d = ff(d, a, b, c, buffer.getUint32(36, true), 12, 0x8b44f7af); - c = ff(c, d, a, b, buffer.getUint32(40, true), 17, 0xffff5bb1); - b = ff(b, c, d, a, buffer.getUint32(44, true), 22, 0x895cd7be); - a = ff(a, b, c, d, buffer.getUint32(48, true), 7, 0x6b901122); - d = ff(d, a, b, c, buffer.getUint32(52, true), 12, 0xfd987193); - c = ff(c, d, a, b, buffer.getUint32(56, true), 17, 0xa679438e); - b = ff(b, c, d, a, buffer.getUint32(60, true), 22, 0x49b40821); - - a = gg(a, b, c, d, buffer.getUint32(4, true), 5, 0xf61e2562); - d = gg(d, a, b, c, buffer.getUint32(24, true), 9, 0xc040b340); - c = gg(c, d, a, b, buffer.getUint32(44, true), 14, 0x265e5a51); - b = gg(b, c, d, a, buffer.getUint32(0, true), 20, 0xe9b6c7aa); - a = gg(a, b, c, d, buffer.getUint32(20, true), 5, 0xd62f105d); - d = gg(d, a, b, c, buffer.getUint32(40, true), 9, 0x02441453); - c = gg(c, d, a, b, buffer.getUint32(60, true), 14, 0xd8a1e681); - b = gg(b, c, d, a, buffer.getUint32(16, true), 20, 0xe7d3fbc8); - a = gg(a, b, c, d, buffer.getUint32(36, true), 5, 0x21e1cde6); - d = gg(d, a, b, c, buffer.getUint32(56, true), 9, 0xc33707d6); - c = gg(c, d, a, b, buffer.getUint32(12, true), 14, 0xf4d50d87); - b = gg(b, c, d, a, buffer.getUint32(32, true), 20, 0x455a14ed); - a = gg(a, b, c, d, buffer.getUint32(52, true), 5, 0xa9e3e905); - d = gg(d, a, b, c, buffer.getUint32(8, true), 9, 0xfcefa3f8); - c = gg(c, d, a, b, buffer.getUint32(28, true), 14, 0x676f02d9); - b = gg(b, c, d, a, buffer.getUint32(48, true), 20, 0x8d2a4c8a); - - a = hh(a, b, c, d, buffer.getUint32(20, true), 4, 0xfffa3942); - d = hh(d, a, b, c, buffer.getUint32(32, true), 11, 0x8771f681); - c = hh(c, d, a, b, buffer.getUint32(44, true), 16, 0x6d9d6122); - b = hh(b, c, d, a, buffer.getUint32(56, true), 23, 0xfde5380c); - a = hh(a, b, c, d, buffer.getUint32(4, true), 4, 0xa4beea44); - d = hh(d, a, b, c, buffer.getUint32(16, true), 11, 0x4bdecfa9); - c = hh(c, d, a, b, buffer.getUint32(28, true), 16, 0xf6bb4b60); - b = hh(b, c, d, a, buffer.getUint32(40, true), 23, 0xbebfbc70); - a = hh(a, b, c, d, buffer.getUint32(52, true), 4, 0x289b7ec6); - d = hh(d, a, b, c, buffer.getUint32(0, true), 11, 0xeaa127fa); - c = hh(c, d, a, b, buffer.getUint32(12, true), 16, 0xd4ef3085); - b = hh(b, c, d, a, buffer.getUint32(24, true), 23, 0x04881d05); - a = hh(a, b, c, d, buffer.getUint32(36, true), 4, 0xd9d4d039); - d = hh(d, a, b, c, buffer.getUint32(48, true), 11, 0xe6db99e5); - c = hh(c, d, a, b, buffer.getUint32(60, true), 16, 0x1fa27cf8); - b = hh(b, c, d, a, buffer.getUint32(8, true), 23, 0xc4ac5665); - - a = ii(a, b, c, d, buffer.getUint32(0, true), 6, 0xf4292244); - d = ii(d, a, b, c, buffer.getUint32(28, true), 10, 0x432aff97); - c = ii(c, d, a, b, buffer.getUint32(56, true), 15, 0xab9423a7); - b = ii(b, c, d, a, buffer.getUint32(20, true), 21, 0xfc93a039); - a = ii(a, b, c, d, buffer.getUint32(48, true), 6, 0x655b59c3); - d = ii(d, a, b, c, buffer.getUint32(12, true), 10, 0x8f0ccc92); - c = ii(c, d, a, b, buffer.getUint32(40, true), 15, 0xffeff47d); - b = ii(b, c, d, a, buffer.getUint32(4, true), 21, 0x85845dd1); - a = ii(a, b, c, d, buffer.getUint32(32, true), 6, 0x6fa87e4f); - d = ii(d, a, b, c, buffer.getUint32(60, true), 10, 0xfe2ce6e0); - c = ii(c, d, a, b, buffer.getUint32(24, true), 15, 0xa3014314); - b = ii(b, c, d, a, buffer.getUint32(52, true), 21, 0x4e0811a1); - a = ii(a, b, c, d, buffer.getUint32(16, true), 6, 0xf7537e82); - d = ii(d, a, b, c, buffer.getUint32(44, true), 10, 0xbd3af235); - c = ii(c, d, a, b, buffer.getUint32(8, true), 15, 0x2ad7d2bb); - b = ii(b, c, d, a, buffer.getUint32(36, true), 21, 0xeb86d391); - - state[0] = (a + state[0]) & 0xffffffff; - state[1] = (b + state[1]) & 0xffffffff; - state[2] = (c + state[2]) & 0xffffffff; - state[3] = (d + state[3]) & 0xffffffff; - } - - reset(): void { - this.state = Uint32Array.from(INIT); - this.buffer = new DataView(new ArrayBuffer(BLOCK_SIZE)); - this.bufferLength = 0; - this.bytesHashed = 0; - this.finished = false; - } -} - -function cmn(q: number, a: number, b: number, x: number, s: number, t: number) { - a = (((a + q) & 0xffffffff) + ((x + t) & 0xffffffff)) & 0xffffffff; - return (((a << s) | (a >>> (32 - s))) + b) & 0xffffffff; -} - -function ff(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { - return cmn((b & c) | (~b & d), a, b, x, s, t); -} - -function gg(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { - return cmn((b & d) | (c & ~d), a, b, x, s, t); -} - -function hh(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { - return cmn(b ^ c ^ d, a, b, x, s, t); -} - -function ii(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { - return cmn(c ^ (b | ~d), a, b, x, s, t); -} - -function isEmptyData(data: SourceData): boolean { - if (typeof data === "string") { - return data.length === 0; - } - - return data.byteLength === 0; -} - -function convertToBuffer(data: SourceData): Uint8Array { - if (typeof data === "string") { - return fromUtf8(data); - } - - if (ArrayBuffer.isView(data)) { - return new Uint8Array(data.buffer, data.byteOffset, data.byteLength / Uint8Array.BYTES_PER_ELEMENT); - } - - return new Uint8Array(data); -} +export * from "@smithy/md5-js"; diff --git a/packages/middleware-apply-body-checksum/jest.config.js b/packages/middleware-apply-body-checksum/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/middleware-apply-body-checksum/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/middleware-apply-body-checksum/package.json b/packages/middleware-apply-body-checksum/package.json index bff0ace79122..d0b9e190468d 100644 --- a/packages/middleware-apply-body-checksum/package.json +++ b/packages/middleware-apply-body-checksum/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest --coverage" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,9 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/is-array-buffer": "*", - "@aws-sdk/protocol-http": "*", - "@aws-sdk/types": "*", + "@smithy/middleware-apply-body-checksum": "^1.0.1", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.spec.ts b/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.spec.ts deleted file mode 100644 index a26ae92a3062..000000000000 --- a/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.spec.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { HttpRequest } from "@aws-sdk/protocol-http"; -import { ChecksumConstructor } from "@aws-sdk/types"; - -import { applyMd5BodyChecksumMiddleware } from "./applyMd5BodyChecksumMiddleware"; - -describe("applyMd5BodyChecksumMiddleware", () => { - const mockEncoder = jest.fn().mockReturnValue("encoded"); - const mockHashUpdate = jest.fn(); - const mockHashDigest = jest.fn().mockReturnValue(new Uint8Array(0)); - const mockHashReset = jest.fn(); - const MockHash: ChecksumConstructor = class {} as any; - MockHash.prototype.update = mockHashUpdate; - MockHash.prototype.digest = mockHashDigest; - MockHash.prototype.reset = mockHashReset; - - const next = jest.fn(); - - class ExoticStream {} - - beforeEach(() => { - mockEncoder.mockClear(); - mockHashUpdate.mockClear(); - mockHashDigest.mockClear(); - mockHashReset.mockClear(); - next.mockClear(); - }); - - for (const body of ["body", new ArrayBuffer(10), new Uint8Array(10), void 0]) { - it("should calculate the body hash, encode the result, and set the encoded hash to content-md5 header", 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]; - expect(request.headers["content-md5"]).toBe("encoded"); - expect(mockHashUpdate.mock.calls).toEqual([[body || ""]]); - }); - - it("should do nothing if a case-insenitive match for the desired header has already been set", 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, - headers: { - "CoNtEnT-Md5": "foo", - }, - }), - }); - - expect(next.mock.calls.length).toBe(1); - const { request } = next.mock.calls[0][0]; - expect(request.headers["CoNtEnT-Md5"]).toBe("foo"); - expect(request.headers["content-md5"]).toBe(undefined); - expect(mockHashUpdate.mock.calls.length).toBe(0); - expect(mockHashDigest.mock.calls.length).toBe(0); - expect(mockEncoder.mock.calls.length).toBe(0); - }); - } - - it("should use the supplied stream hasher to calculate the hash of a streaming body", 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: new ExoticStream(), - }), - }); - - expect(next.mock.calls.length).toBe(1); - const { request } = next.mock.calls[0][0]; - expect(request.body).toStrictEqual(new ExoticStream()); - expect(request.headers["content-md5"]).toBe("encoded"); - expect(mockHashDigest.mock.calls.length).toBe(0); - expect(mockEncoder.mock.calls.length).toBe(1); - expect(mockEncoder.mock.calls).toEqual([[new Uint8Array(5)]]); - }); -}); diff --git a/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.ts b/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.ts deleted file mode 100644 index 23661c9dcd27..000000000000 --- a/packages/middleware-apply-body-checksum/src/applyMd5BodyChecksumMiddleware.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { isArrayBuffer } from "@aws-sdk/is-array-buffer"; -import { HttpRequest } from "@aws-sdk/protocol-http"; -import { - BuildHandler, - BuildHandlerArguments, - BuildHandlerOptions, - BuildHandlerOutput, - BuildMiddleware, - HeaderBag, - MetadataBearer, - Pluggable, -} from "@aws-sdk/types"; - -import { Md5BodyChecksumResolvedConfig } from "./md5Configuration"; - -export const applyMd5BodyChecksumMiddleware = - (options: Md5BodyChecksumResolvedConfig): BuildMiddleware => - (next: BuildHandler): BuildHandler => - async (args: BuildHandlerArguments): Promise> => { - let { request } = args; - if (HttpRequest.isInstance(request)) { - const { body, headers } = request; - if (!hasHeader("content-md5", headers)) { - let digest: Promise; - if (body === undefined || typeof body === "string" || ArrayBuffer.isView(body) || isArrayBuffer(body)) { - const hash = new options.md5(); - hash.update(body || ""); - digest = hash.digest(); - } else { - digest = options.streamHasher(options.md5, body); - } - - request = { - ...request, - headers: { - ...headers, - "content-md5": options.base64Encoder(await digest), - }, - }; - } - } - return next({ - ...args, - request, - }); - }; - -export const applyMd5BodyChecksumMiddlewareOptions: BuildHandlerOptions = { - name: "applyMd5BodyChecksumMiddleware", - step: "build", - tags: ["SET_CONTENT_MD5", "BODY_CHECKSUM"], - override: true, -}; - -export const getApplyMd5BodyChecksumPlugin = (config: Md5BodyChecksumResolvedConfig): Pluggable => ({ - applyToStack: (clientStack) => { - clientStack.add(applyMd5BodyChecksumMiddleware(config), applyMd5BodyChecksumMiddlewareOptions); - }, -}); - -const hasHeader = (soughtHeader: string, headers: HeaderBag): boolean => { - soughtHeader = soughtHeader.toLowerCase(); - for (const headerName of Object.keys(headers)) { - if (soughtHeader === headerName.toLowerCase()) { - return true; - } - } - - return false; -}; diff --git a/packages/middleware-apply-body-checksum/src/index.spec.ts b/packages/middleware-apply-body-checksum/src/index.spec.ts deleted file mode 100644 index 4195e575a303..000000000000 --- a/packages/middleware-apply-body-checksum/src/index.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { applyMd5BodyChecksumMiddleware } from "./index"; - -describe("middleware-apply-body-checksum package exports", () => { - it("applyMd5BodyChecksumMiddleware", () => { - expect(typeof applyMd5BodyChecksumMiddleware).toBe("function"); - }); -}); diff --git a/packages/middleware-apply-body-checksum/src/index.ts b/packages/middleware-apply-body-checksum/src/index.ts index a28c2dda3167..819cd4812947 100644 --- a/packages/middleware-apply-body-checksum/src/index.ts +++ b/packages/middleware-apply-body-checksum/src/index.ts @@ -1,2 +1 @@ -export * from "./applyMd5BodyChecksumMiddleware"; -export * from "./md5Configuration"; +export * from "@smithy/middleware-apply-body-checksum"; diff --git a/packages/middleware-apply-body-checksum/src/md5Configuration.ts b/packages/middleware-apply-body-checksum/src/md5Configuration.ts deleted file mode 100644 index 96213e0af23a..000000000000 --- a/packages/middleware-apply-body-checksum/src/md5Configuration.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ChecksumConstructor, Encoder, HashConstructor, StreamHasher } from "@aws-sdk/types"; - -export interface Md5BodyChecksumInputConfig {} -interface PreviouslyResolved { - md5: ChecksumConstructor | HashConstructor; - base64Encoder: Encoder; - streamHasher: StreamHasher; -} - -export interface Md5BodyChecksumResolvedConfig { - /** - * A constructor for a class implementing the @aws-sdk/types.Hash interface that computes MD5 hashes. - * @internal - */ - md5: ChecksumConstructor | HashConstructor; - /** - * The function that will be used to convert binary data to a base64-encoded string. - * @internal - */ - base64Encoder: Encoder; - /** - * A function that, given a hash constructor and a stream, calculates the hash of the streamed value. - * @internal - */ - streamHasher: StreamHasher; -} - -export const resolveMd5BodyChecksumConfig = ( - input: T & PreviouslyResolved & Md5BodyChecksumInputConfig -): T & Md5BodyChecksumResolvedConfig => input; diff --git a/packages/middleware-content-length/jest.config.js b/packages/middleware-content-length/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/middleware-content-length/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/middleware-content-length/package.json b/packages/middleware-content-length/package.json index 7c491a4d78e0..1703b935d445 100644 --- a/packages/middleware-content-length/package.json +++ b/packages/middleware-content-length/package.json @@ -20,8 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/protocol-http": "*", - "@aws-sdk/types": "*", + "@smithy/middleware-content-length": "^1.0.1", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/middleware-content-length/src/index.ts b/packages/middleware-content-length/src/index.ts index f79a59de0ec6..64bc117ecc03 100644 --- a/packages/middleware-content-length/src/index.ts +++ b/packages/middleware-content-length/src/index.ts @@ -1,58 +1 @@ -import { HttpRequest } from "@aws-sdk/protocol-http"; -import { - BodyLengthCalculator, - BuildHandler, - BuildHandlerArguments, - BuildHandlerOptions, - BuildHandlerOutput, - BuildMiddleware, - MetadataBearer, - Pluggable, -} from "@aws-sdk/types"; - -const CONTENT_LENGTH_HEADER = "content-length"; - -export function contentLengthMiddleware(bodyLengthChecker: BodyLengthCalculator): BuildMiddleware { - return (next: BuildHandler): BuildHandler => - async (args: BuildHandlerArguments): Promise> => { - const request = args.request; - if (HttpRequest.isInstance(request)) { - const { body, headers } = request; - if ( - body && - Object.keys(headers) - .map((str) => str.toLowerCase()) - .indexOf(CONTENT_LENGTH_HEADER) === -1 - ) { - try { - const length = bodyLengthChecker(body); - request.headers = { - ...request.headers, - [CONTENT_LENGTH_HEADER]: String(length), - }; - } catch (error) { - // ToDo: Add 'transfer-encoding' as chunked only for HTTP/1.1 request - // Refs: https://github.com/aws/aws-sdk-js-v3/pull/3403 - } - } - } - - return next({ - ...args, - request, - }); - }; -} - -export const contentLengthMiddlewareOptions: BuildHandlerOptions = { - step: "build", - tags: ["SET_CONTENT_LENGTH", "CONTENT_LENGTH"], - name: "contentLengthMiddleware", - override: true, -}; - -export const getContentLengthPlugin = (options: { bodyLengthChecker: BodyLengthCalculator }): Pluggable => ({ - applyToStack: (clientStack) => { - clientStack.add(contentLengthMiddleware(options.bodyLengthChecker), contentLengthMiddlewareOptions); - }, -}); +export * from "@smithy/middleware-content-length"; diff --git a/packages/middleware-endpoint/jest.config.integ.js b/packages/middleware-endpoint/jest.config.integ.js deleted file mode 100644 index d09aba7398c7..000000000000 --- a/packages/middleware-endpoint/jest.config.integ.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - preset: "ts-jest", - testMatch: ["**/*.integ.spec.ts"], -}; diff --git a/packages/middleware-endpoint/jest.config.js b/packages/middleware-endpoint/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/middleware-endpoint/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/middleware-endpoint/package.json b/packages/middleware-endpoint/package.json index d4bf92a8a734..4b051056ed96 100644 --- a/packages/middleware-endpoint/package.json +++ b/packages/middleware-endpoint/package.json @@ -9,8 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest --passWithNoTests", - "test:integration": "jest -c jest.config.integ.js" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -21,10 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-serde": "*", - "@aws-sdk/types": "*", - "@aws-sdk/url-parser": "*", - "@aws-sdk/util-middleware": "*", + "@smithy/middleware-endpoint": "^1.0.2", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.spec.ts b/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.spec.ts deleted file mode 100644 index ab43d95ca456..000000000000 --- a/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Endpoint } from "@aws-sdk/types"; - -import { createConfigValueProvider } from "./createConfigValueProvider"; - -describe(createConfigValueProvider.name, () => { - it("should create a normalized provider for any config value", async () => { - const config = { - a: 1, - b: 2, - }; - expect(await createConfigValueProvider("a", "a", config)()).toEqual(1); - }); - - it("should look up both the canonical Endpoint ruleset param name and any localized override", async () => { - const config = { - a: 1, - b: 2, - }; - expect(await createConfigValueProvider("a", "x", config)()).toEqual(1); - expect(await createConfigValueProvider("x", "a", config)()).toEqual(1); - }); - - it("should normalize endpoint objects into URLs", async () => { - const sampleUrl = "https://aws.amazon.com/"; - const config = { - str: sampleUrl, - v1: { - protocol: "https:", - hostname: new URL(sampleUrl).hostname, - path: "/", - } as Endpoint, - v2: { url: new URL(sampleUrl) }, - }; - expect(await createConfigValueProvider("str", "endpoint", config)()).toEqual(sampleUrl); - expect(await createConfigValueProvider("v1", "endpoint", config)()).toEqual(sampleUrl); - expect(await createConfigValueProvider("v2", "endpoint", config)()).toEqual(sampleUrl); - }); -}); diff --git a/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.ts b/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.ts deleted file mode 100644 index 2f6f24c1743d..000000000000 --- a/packages/middleware-endpoint/src/adaptors/createConfigValueProvider.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Endpoint, EndpointV2 } from "@aws-sdk/types"; - -/** - * Normalize some key of the client config to an async provider. - * @internal - * - * @param configKey - the key to look up in config. - * @param canonicalEndpointParamKey - this is the name the EndpointRuleSet uses. - * it will most likely not contain the config - * value, but we use it as a fallback. - * @param config - container of the config values. - * - * @returns async function that will resolve with the value. - */ -export const createConfigValueProvider = >( - configKey: string, - canonicalEndpointParamKey: string, - config: Config -) => { - const configProvider = async () => { - const configValue: unknown = config[configKey] ?? config[canonicalEndpointParamKey]; - if (typeof configValue === "function") { - return configValue(); - } - return configValue; - }; - if (configKey === "endpoint" || canonicalEndpointParamKey === "endpoint") { - return async () => { - const endpoint = await configProvider(); - if (endpoint && typeof endpoint === "object") { - if ("url" in endpoint) { - return (endpoint as EndpointV2).url.href; - } - if ("hostname" in endpoint) { - const { protocol, hostname, port, path } = endpoint as Endpoint; - // query params are ignored in setting endpoint. - return `${protocol}//${hostname}${port ? ":" + port : ""}${path}`; - } - } - return endpoint; - }; - } - return configProvider; -}; diff --git a/packages/middleware-endpoint/src/adaptors/getEndpointFromInstructions.ts b/packages/middleware-endpoint/src/adaptors/getEndpointFromInstructions.ts deleted file mode 100644 index fea87427b167..000000000000 --- a/packages/middleware-endpoint/src/adaptors/getEndpointFromInstructions.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { EndpointParameters, EndpointV2, HandlerExecutionContext } from "@aws-sdk/types"; - -import { EndpointResolvedConfig } from "../resolveEndpointConfig"; -import { resolveParamsForS3 } from "../service-customizations"; -import { EndpointParameterInstructions } from "../types"; -import { createConfigValueProvider } from "./createConfigValueProvider"; - -/** - * @internal - */ -export type EndpointParameterInstructionsSupplier = Partial<{ - getEndpointParameterInstructions(): EndpointParameterInstructions; -}>; - -/** - * This step in the endpoint resolution process is exposed as a function - * to allow packages such as signers, lib-upload, etc. to get - * the V2 Endpoint associated to an instance of some api operation command - * without needing to send it or resolve its middleware stack. - * - * @internal - * @param commandInput - the input of the Command in question. - * @param instructionsSupplier - this is typically a Command constructor. A static function supplying the - * endpoint parameter instructions will exist for commands in services - * having an endpoints ruleset trait. - * @param clientConfig - config of the service client. - * @param context - optional context. - */ -export const getEndpointFromInstructions = async < - T extends EndpointParameters, - CommandInput extends Record, - Config extends Record ->( - commandInput: CommandInput, - instructionsSupplier: EndpointParameterInstructionsSupplier, - clientConfig: Partial> & Config, - context?: HandlerExecutionContext -): Promise => { - const endpointParams = await resolveParams(commandInput, instructionsSupplier, clientConfig); - - if (typeof clientConfig.endpointProvider !== "function") { - throw new Error("config.endpointProvider is not set."); - } - const endpoint: EndpointV2 = clientConfig.endpointProvider!(endpointParams as T, context); - - return endpoint; -}; - -/** - * @internal - */ -export const resolveParams = async < - T extends EndpointParameters, - CommandInput extends Record, - Config extends Record ->( - commandInput: CommandInput, - instructionsSupplier: EndpointParameterInstructionsSupplier, - clientConfig: Partial> & Config -) => { - const endpointParams: EndpointParameters = {}; - const instructions: EndpointParameterInstructions = instructionsSupplier?.getEndpointParameterInstructions?.() || {}; - - for (const [name, instruction] of Object.entries(instructions)) { - switch (instruction.type) { - case "staticContextParams": - endpointParams[name] = instruction.value; - break; - case "contextParams": - endpointParams[name] = commandInput[instruction.name] as string | boolean; - break; - case "clientContextParams": - case "builtInParams": - endpointParams[name] = await createConfigValueProvider(instruction.name, name, clientConfig)(); - break; - default: - throw new Error("Unrecognized endpoint parameter instruction: " + JSON.stringify(instruction)); - } - } - - if (Object.keys(instructions).length === 0) { - Object.assign(endpointParams, clientConfig); - } - - if (String(clientConfig.serviceId).toLowerCase() === "s3") { - await resolveParamsForS3(endpointParams); - } - - return endpointParams; -}; diff --git a/packages/middleware-endpoint/src/adaptors/index.ts b/packages/middleware-endpoint/src/adaptors/index.ts deleted file mode 100644 index cc1348838f6c..000000000000 --- a/packages/middleware-endpoint/src/adaptors/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @internal - */ -export * from "./getEndpointFromInstructions"; -/** - * @internal - */ -export * from "./toEndpointV1"; diff --git a/packages/middleware-endpoint/src/adaptors/toEndpointV1.ts b/packages/middleware-endpoint/src/adaptors/toEndpointV1.ts deleted file mode 100644 index 05346437d879..000000000000 --- a/packages/middleware-endpoint/src/adaptors/toEndpointV1.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Endpoint, EndpointV2 } from "@aws-sdk/types"; -import { parseUrl } from "@aws-sdk/url-parser"; - -/** - * @internal - */ -export const toEndpointV1 = (endpoint: string | Endpoint | EndpointV2): Endpoint => { - if (typeof endpoint === "object") { - if ("url" in endpoint) { - // v2 - return parseUrl(endpoint.url); - } - // v1 - return endpoint; - } - return parseUrl(endpoint); -}; diff --git a/packages/middleware-endpoint/src/endpointMiddleware.ts b/packages/middleware-endpoint/src/endpointMiddleware.ts deleted file mode 100644 index ade1cdc1c082..000000000000 --- a/packages/middleware-endpoint/src/endpointMiddleware.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - AuthScheme, - EndpointParameters, - EndpointV2, - HandlerExecutionContext, - MetadataBearer, - SerializeHandler, - SerializeHandlerArguments, - SerializeHandlerOutput, - SerializeMiddleware, -} from "@aws-sdk/types"; - -import { getEndpointFromInstructions } from "./adaptors/getEndpointFromInstructions"; -import { EndpointResolvedConfig } from "./resolveEndpointConfig"; -import { EndpointParameterInstructions } from "./types"; - -/** - * @internal - */ -export const endpointMiddleware = ({ - config, - instructions, -}: { - config: EndpointResolvedConfig; - instructions: EndpointParameterInstructions; -}): SerializeMiddleware => { - return ( - next: SerializeHandler, - context: HandlerExecutionContext - ): SerializeHandler => - async (args: SerializeHandlerArguments): Promise> => { - const endpoint: EndpointV2 = await getEndpointFromInstructions( - args.input, - { - getEndpointParameterInstructions() { - return instructions; - }, - }, - { ...config }, - context - ); - - context.endpointV2 = endpoint; - context.authSchemes = endpoint.properties?.authSchemes; - - const authScheme: AuthScheme | undefined = context.authSchemes?.[0]; - if (authScheme) { - context["signing_region"] = authScheme.signingRegion; - context["signing_service"] = authScheme.signingName; - } - - return next({ - ...args, - }); - }; -}; diff --git a/packages/middleware-endpoint/src/getEndpointPlugin.ts b/packages/middleware-endpoint/src/getEndpointPlugin.ts deleted file mode 100644 index 09708e5be1d0..000000000000 --- a/packages/middleware-endpoint/src/getEndpointPlugin.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { serializerMiddlewareOption } from "@aws-sdk/middleware-serde"; -import { EndpointParameters, Pluggable, RelativeMiddlewareOptions, SerializeHandlerOptions } from "@aws-sdk/types"; - -import { endpointMiddleware } from "./endpointMiddleware"; -import { EndpointResolvedConfig } from "./resolveEndpointConfig"; -import { EndpointParameterInstructions } from "./types"; - -/** - * @internal - */ -export const endpointMiddlewareOptions: SerializeHandlerOptions & RelativeMiddlewareOptions = { - step: "serialize", - tags: ["ENDPOINT_PARAMETERS", "ENDPOINT_V2", "ENDPOINT"], - name: "endpointV2Middleware", - override: true, - relation: "before", - toMiddleware: serializerMiddlewareOption.name!, -}; - -/** - * @internal - */ -export const getEndpointPlugin = ( - config: EndpointResolvedConfig, - instructions: EndpointParameterInstructions -): Pluggable => ({ - applyToStack: (clientStack) => { - clientStack.addRelativeTo( - endpointMiddleware({ - config, - instructions, - }), - endpointMiddlewareOptions - ); - }, -}); diff --git a/packages/middleware-endpoint/src/index.ts b/packages/middleware-endpoint/src/index.ts index 40e6bdc6ed09..093fded0e688 100644 --- a/packages/middleware-endpoint/src/index.ts +++ b/packages/middleware-endpoint/src/index.ts @@ -1,20 +1 @@ -/** - * @internal - */ -export * from "./adaptors"; -/** - * @internal - */ -export * from "./endpointMiddleware"; -/** - * @internal - */ -export * from "./getEndpointPlugin"; -/** - * @internal - */ -export * from "./resolveEndpointConfig"; -/** - * @internal - */ -export * from "./types"; +export * from "@smithy/middleware-endpoint"; diff --git a/packages/middleware-endpoint/src/resolveEndpointConfig.ts b/packages/middleware-endpoint/src/resolveEndpointConfig.ts deleted file mode 100644 index ef716cb430a6..000000000000 --- a/packages/middleware-endpoint/src/resolveEndpointConfig.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { Endpoint, EndpointParameters, EndpointV2, Logger, Provider, UrlParser } from "@aws-sdk/types"; -import { normalizeProvider } from "@aws-sdk/util-middleware"; - -import { toEndpointV1 } from "./adaptors/toEndpointV1"; - -/** - * @internal - * - * Endpoint config interfaces and resolver for Endpoint v2. They live in separate package to allow per-service onboarding. - * When all services onboard Endpoint v2, the resolver in config-resolver package can be removed. - * This interface includes all the endpoint parameters with built-in bindings of "AWS::*" and "SDK::*" - */ -export interface EndpointInputConfig { - /** - * The fully qualified endpoint of the webservice. This is only for using - * a custom endpoint (for example, when using a local version of S3). - * - * Endpoint transformations such as S3 applying a bucket to the hostname are - * still applicable to this custom endpoint. - */ - endpoint?: string | Endpoint | Provider | EndpointV2 | Provider; - - /** - * Providing a custom endpointProvider will override - * built-in transformations of the endpoint such as S3 adding the bucket - * name to the hostname, since they are part of the default endpointProvider. - */ - endpointProvider?: (params: T, context?: { logger?: Logger }) => EndpointV2; - - /** - * Whether TLS is enabled for requests. - * @deprecated - */ - tls?: boolean; - - /** - * Enables IPv6/IPv4 dualstack endpoint. - */ - useDualstackEndpoint?: boolean | Provider; - - /** - * Enables FIPS compatible endpoints. - */ - useFipsEndpoint?: boolean | Provider; -} - -interface PreviouslyResolved { - urlParser: UrlParser; - region: Provider; - endpointProvider: (params: T, context?: { logger?: Logger }) => EndpointV2; - logger?: Logger; -} - -/** - * @internal - * - * This supercedes the similarly named EndpointsResolvedConfig (no parametric types) - * from resolveEndpointsConfig.ts in @aws-sdk/config-resolver. - */ -export interface EndpointResolvedConfig { - /** - * Custom endpoint provided by the user. - * This is normalized to a single interface from the various acceptable types. - * This field will be undefined if a custom endpoint is not provided. - */ - endpoint?: Provider; - - endpointProvider: (params: T, context?: { logger?: Logger }) => EndpointV2; - - /** - * Whether TLS is enabled for requests. - * @deprecated - */ - tls: boolean; - - /** - * Whether the endpoint is specified by caller. - * @internal - * @deprecated - */ - isCustomEndpoint?: boolean; - - /** - * Resolved value for input {@link EndpointsInputConfig.useDualstackEndpoint} - */ - useDualstackEndpoint: Provider; - - /** - * Resolved value for input {@link EndpointsInputConfig.useFipsEndpoint} - */ - useFipsEndpoint: Provider; -} - -/** - * @internal - */ -export const resolveEndpointConfig = ( - input: T & EndpointInputConfig

& PreviouslyResolved

-): T & EndpointResolvedConfig

=> { - const tls = input.tls ?? true; - const { endpoint } = input; - - const customEndpointProvider = - endpoint != null ? async () => toEndpointV1(await normalizeProvider(endpoint)()) : undefined; - - const isCustomEndpoint = !!endpoint; - - return { - ...input, - endpoint: customEndpointProvider, - tls, - isCustomEndpoint, - useDualstackEndpoint: normalizeProvider(input.useDualstackEndpoint ?? false), - useFipsEndpoint: normalizeProvider(input.useFipsEndpoint ?? false), - } as T & EndpointResolvedConfig

; -}; diff --git a/packages/middleware-endpoint/src/service-customizations/index.ts b/packages/middleware-endpoint/src/service-customizations/index.ts deleted file mode 100644 index 716a15d42e15..000000000000 --- a/packages/middleware-endpoint/src/service-customizations/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * @internal - */ -export * from "./s3"; diff --git a/packages/middleware-endpoint/src/service-customizations/s3.ts b/packages/middleware-endpoint/src/service-customizations/s3.ts deleted file mode 100644 index 2bc9acaf9ccd..000000000000 --- a/packages/middleware-endpoint/src/service-customizations/s3.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { EndpointParameters } from "@aws-sdk/types"; - -/** - * @internal - */ -export const resolveParamsForS3 = async (endpointParams: EndpointParameters) => { - const bucket = (endpointParams?.Bucket as string) || ""; - - if (typeof endpointParams.Bucket === "string") { - endpointParams.Bucket = bucket.replace(/#/g, encodeURIComponent("#")).replace(/\?/g, encodeURIComponent("?")); - } - - if (isArnBucketName(bucket)) { - if (endpointParams.ForcePathStyle === true) { - throw new Error("Path-style addressing cannot be used with ARN buckets"); - } - } else if ( - !isDnsCompatibleBucketName(bucket) || - (bucket.indexOf(".") !== -1 && !String(endpointParams.Endpoint).startsWith("http:")) || - bucket.toLowerCase() !== bucket || - bucket.length < 3 - ) { - endpointParams.ForcePathStyle = true; - } - - if (endpointParams.DisableMultiRegionAccessPoints) { - // inconsistent naming - endpointParams.disableMultiRegionAccessPoints = true; - endpointParams.DisableMRAP = true; - } - - return endpointParams; -}; - -const DOMAIN_PATTERN = /^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$/; -const IP_ADDRESS_PATTERN = /(\d+\.){3}\d+/; -const DOTS_PATTERN = /\.\./; -/** - * @internal - */ -export const DOT_PATTERN = /\./; -/** - * @internal - */ -export const S3_HOSTNAME_PATTERN = /^(.+\.)?s3(-fips)?(\.dualstack)?[.-]([a-z0-9-]+)\./; - -/** - * Determines whether a given string is DNS compliant per the rules outlined by - * S3. Length, capitaization, and leading dot restrictions are enforced by the - * DOMAIN_PATTERN regular expression. - * @internal - * - * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html - */ -export const isDnsCompatibleBucketName = (bucketName: string): boolean => - DOMAIN_PATTERN.test(bucketName) && !IP_ADDRESS_PATTERN.test(bucketName) && !DOTS_PATTERN.test(bucketName); - -/** - * @internal - */ -export const isArnBucketName = (bucketName: string): boolean => { - const [arn, partition, service, region, account, typeOrId] = bucketName.split(":"); - const isArn = arn === "arn" && bucketName.split(":").length >= 6; - const isValidArn = [arn, partition, service, account, typeOrId].filter(Boolean).length === 5; - if (isArn && !isValidArn) { - throw new Error(`Invalid ARN: ${bucketName} was an invalid ARN.`); - } - return arn === "arn" && !!partition && !!service && !!account && !!typeOrId; -}; diff --git a/packages/middleware-endpoint/src/types.ts b/packages/middleware-endpoint/src/types.ts deleted file mode 100644 index 54951699b591..000000000000 --- a/packages/middleware-endpoint/src/types.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @internal - */ -export interface EndpointParameterInstructions { - [name: string]: - | BuiltInParamInstruction - | ClientContextParamInstruction - | StaticContextParamInstruction - | ContextParamInstruction; -} - -/** - * @internal - */ -export interface BuiltInParamInstruction { - type: "builtInParams"; - name: string; -} - -/** - * @internal - */ -export interface ClientContextParamInstruction { - type: "clientContextParams"; - name: string; // The client resolved config name that has clientContextParams trait -} - -/** - * @internal - */ -export interface StaticContextParamInstruction { - type: "staticContextParams"; - value: string | boolean; -} - -/** - * @internal - */ -export interface ContextParamInstruction { - type: "contextParams"; - name: string; // The input structure's member name that has contextParams trait -} diff --git a/packages/middleware-retry/jest.config.js b/packages/middleware-retry/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/middleware-retry/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/middleware-retry/package.json b/packages/middleware-retry/package.json index 35727190e077..110bdef756cc 100644 --- a/packages/middleware-retry/package.json +++ b/packages/middleware-retry/package.json @@ -10,7 +10,7 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "extract:docs": "api-extractor run --local", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -21,11 +21,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/protocol-http": "*", - "@aws-sdk/service-error-classification": "*", - "@aws-sdk/types": "*", - "@aws-sdk/util-middleware": "*", - "@aws-sdk/util-retry": "*", + "@smithy/middleware-retry": "^1.0.3", "tslib": "^2.5.0", "uuid": "^8.3.2" }, diff --git a/packages/middleware-retry/src/AdaptiveRetryStrategy.spec.ts b/packages/middleware-retry/src/AdaptiveRetryStrategy.spec.ts deleted file mode 100644 index d121fe8575e0..000000000000 --- a/packages/middleware-retry/src/AdaptiveRetryStrategy.spec.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { DefaultRateLimiter, RateLimiter, RETRY_MODES } from "@aws-sdk/util-retry"; - -import { AdaptiveRetryStrategy } from "./AdaptiveRetryStrategy"; -import { StandardRetryStrategy } from "./StandardRetryStrategy"; -import { RetryQuota } from "./types"; - -jest.mock("./StandardRetryStrategy"); -jest.mock("@aws-sdk/util-retry"); - -describe(AdaptiveRetryStrategy.name, () => { - const maxAttemptsProvider = jest.fn(); - const mockDefaultRateLimiter = { - getSendToken: jest.fn(), - updateClientSendingRate: jest.fn(), - }; - - beforeEach(() => { - (DefaultRateLimiter as jest.Mock).mockReturnValue(mockDefaultRateLimiter); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("constructor", () => { - it("calls super constructor", () => { - const retryDecider = jest.fn(); - const delayDecider = jest.fn(); - const retryQuota = {} as RetryQuota; - const rateLimiter = {} as RateLimiter; - - new AdaptiveRetryStrategy(maxAttemptsProvider, { - retryDecider, - delayDecider, - retryQuota, - rateLimiter, - }); - expect(StandardRetryStrategy).toHaveBeenCalledWith(maxAttemptsProvider, { - retryDecider, - delayDecider, - retryQuota, - }); - }); - - it(`sets mode=${RETRY_MODES.ADAPTIVE}`, () => { - const retryStrategy = new AdaptiveRetryStrategy(maxAttemptsProvider); - expect(retryStrategy.mode).toStrictEqual(RETRY_MODES.ADAPTIVE); - }); - - describe("rateLimiter init", () => { - it("sets getDefaultrateLimiter if options is undefined", () => { - const retryStrategy = new AdaptiveRetryStrategy(maxAttemptsProvider); - expect(retryStrategy["rateLimiter"]).toBe(mockDefaultRateLimiter); - }); - - it("sets getDefaultrateLimiter if options.delayDecider undefined", () => { - const retryStrategy = new AdaptiveRetryStrategy(maxAttemptsProvider, {}); - expect(retryStrategy["rateLimiter"]).toBe(mockDefaultRateLimiter); - }); - - it("sets options.rateLimiter if defined", () => { - const rateLimiter = {} as RateLimiter; - const retryStrategy = new AdaptiveRetryStrategy(maxAttemptsProvider, { - rateLimiter, - }); - expect(retryStrategy["rateLimiter"]).toBe(rateLimiter); - }); - }); - }); - - describe("retry", () => { - const mockedSuperRetry = jest.spyOn(StandardRetryStrategy.prototype, "retry"); - - beforeEach(async () => { - const next = jest.fn(); - const retryStrategy = new AdaptiveRetryStrategy(maxAttemptsProvider); - await retryStrategy.retry(next, { request: { headers: {} } } as any); - expect(mockedSuperRetry).toHaveBeenCalledTimes(1); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("calls rateLimiter.getSendToken in beforeRequest", async () => { - expect(mockDefaultRateLimiter.getSendToken).toHaveBeenCalledTimes(0); - await mockedSuperRetry.mock.calls[0][2].beforeRequest(); - expect(mockDefaultRateLimiter.getSendToken).toHaveBeenCalledTimes(1); - }); - - it("calls rateLimiter.updateClientSendingRate in afterRequest", async () => { - expect(mockDefaultRateLimiter.updateClientSendingRate).toHaveBeenCalledTimes(0); - await mockedSuperRetry.mock.calls[0][2].afterRequest(); - expect(mockDefaultRateLimiter.updateClientSendingRate).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/packages/middleware-retry/src/AdaptiveRetryStrategy.ts b/packages/middleware-retry/src/AdaptiveRetryStrategy.ts deleted file mode 100644 index 0ac4cd394934..000000000000 --- a/packages/middleware-retry/src/AdaptiveRetryStrategy.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { FinalizeHandler, FinalizeHandlerArguments, MetadataBearer, Provider } from "@aws-sdk/types"; -import { DefaultRateLimiter, RateLimiter, RETRY_MODES } from "@aws-sdk/util-retry"; - -import { StandardRetryStrategy, StandardRetryStrategyOptions } from "./StandardRetryStrategy"; - -/** - * Strategy options to be passed to AdaptiveRetryStrategy - */ -export interface AdaptiveRetryStrategyOptions extends StandardRetryStrategyOptions { - rateLimiter?: RateLimiter; -} - -/** - * @deprecated use AdaptiveRetryStrategy from @aws-sdk/util-retry - */ -export class AdaptiveRetryStrategy extends StandardRetryStrategy { - private rateLimiter: RateLimiter; - - constructor(maxAttemptsProvider: Provider, options?: AdaptiveRetryStrategyOptions) { - const { rateLimiter, ...superOptions } = options ?? {}; - super(maxAttemptsProvider, superOptions); - this.rateLimiter = rateLimiter ?? new DefaultRateLimiter(); - this.mode = RETRY_MODES.ADAPTIVE; - } - - async retry( - next: FinalizeHandler, - args: FinalizeHandlerArguments - ) { - return super.retry(next, args, { - beforeRequest: async () => { - return this.rateLimiter.getSendToken(); - }, - afterRequest: (response: any) => { - this.rateLimiter.updateClientSendingRate(response); - }, - }); - } -} diff --git a/packages/middleware-retry/src/StandardRetryStrategy.spec.ts b/packages/middleware-retry/src/StandardRetryStrategy.spec.ts deleted file mode 100644 index 67631cd68fad..000000000000 --- a/packages/middleware-retry/src/StandardRetryStrategy.spec.ts +++ /dev/null @@ -1,605 +0,0 @@ -import { HttpRequest, HttpResponse } from "@aws-sdk/protocol-http"; -import { isThrottlingError } from "@aws-sdk/service-error-classification"; -import { - DEFAULT_MAX_ATTEMPTS, - DEFAULT_RETRY_DELAY_BASE, - INITIAL_RETRY_TOKENS, - RETRY_MODES, - THROTTLING_RETRY_DELAY_BASE, -} from "@aws-sdk/util-retry"; -import { v4 } from "uuid"; - -import { getDefaultRetryQuota } from "./defaultRetryQuota"; -import { defaultDelayDecider } from "./delayDecider"; -import { defaultRetryDecider } from "./retryDecider"; -import { StandardRetryStrategy } from "./StandardRetryStrategy"; -import { RetryQuota } from "./types"; - -jest.mock("@aws-sdk/service-error-classification"); -jest.mock("./delayDecider"); -jest.mock("./retryDecider"); -jest.mock("./defaultRetryQuota"); -jest.mock("@aws-sdk/protocol-http"); -jest.mock("uuid"); - -describe("defaultStrategy", () => { - let next: jest.Mock; // variable for next mock function in utility methods - const maxAttempts = 3; - - const mockDefaultRetryQuota = { - hasRetryTokens: jest.fn().mockReturnValue(true), - retrieveRetryTokens: jest.fn().mockReturnValue(1), - releaseRetryTokens: jest.fn(), - }; - - const mockSuccessfulOperation = (maxAttempts: number, options?: { mockResponse?: string }) => { - next = jest.fn().mockResolvedValueOnce({ - response: options?.mockResponse, - output: { $metadata: {} }, - }); - - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - return retryStrategy.retry(next, { request: { headers: {} } } as any); - }; - - const mockFailedOperation = async (maxAttempts: number, options?: { mockError?: Error }) => { - const mockError = options?.mockError ?? new Error("mockError"); - next = jest.fn().mockRejectedValue(mockError); - - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - try { - await retryStrategy.retry(next, { request: { headers: {} } } as any); - } catch (error) { - expect(error).toStrictEqual(mockError); - return error; - } - }; - - const mockSuccessAfterOneFail = (maxAttempts: number, options?: { mockError?: Error; mockResponse?: string }) => { - const mockError = options?.mockError ?? new Error("mockError"); - const mockResponse = { - response: options?.mockResponse, - output: { $metadata: {} }, - }; - - next = jest.fn().mockRejectedValueOnce(mockError).mockResolvedValueOnce(mockResponse); - - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - return retryStrategy.retry(next, { request: { headers: {} } } as any); - }; - - const mockSuccessAfterTwoFails = (maxAttempts: number, options?: { mockError?: Error; mockResponse?: string }) => { - const mockError = options?.mockError ?? new Error("mockError"); - const mockResponse = { - response: options?.mockResponse, - output: { $metadata: {} }, - }; - - next = jest - .fn() - .mockRejectedValueOnce(mockError) - .mockRejectedValueOnce(mockError) - .mockResolvedValueOnce(mockResponse); - - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - return retryStrategy.retry(next, { request: { headers: {} } } as any); - }; - - beforeEach(() => { - (isThrottlingError as jest.Mock).mockReturnValue(true); - (defaultDelayDecider as jest.Mock).mockReturnValue(0); - (defaultRetryDecider as jest.Mock).mockReturnValue(true); - (getDefaultRetryQuota as jest.Mock).mockReturnValue(mockDefaultRetryQuota); - (HttpRequest as unknown as jest.Mock).mockReturnValue({ - isInstance: jest.fn().mockReturnValue(false), - }); - (HttpResponse as unknown as jest.Mock).mockReturnValue({ - isInstance: jest.fn().mockReturnValue(false), - }); - (v4 as jest.Mock).mockReturnValue("42"); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("sets maxAttemptsProvider as class member variable", () => { - [1, 2, 3].forEach((maxAttempts) => { - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - expect(retryStrategy["maxAttemptsProvider"]()).resolves.toBe(maxAttempts); - }); - }); - - it(`sets mode=${RETRY_MODES.STANDARD}`, () => { - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - expect(retryStrategy.mode).toStrictEqual(RETRY_MODES.STANDARD); - }); - - it("handles non-standard errors", () => { - const nonStandardErrors = [undefined, "foo", { foo: "bar" }, 123, false, null]; - const maxAttempts = 1; - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - for (const error of nonStandardErrors) { - next = jest.fn().mockRejectedValue(error); - expect(retryStrategy.retry(next, { request: { headers: {} } } as any)).rejects.toBeInstanceOf(Error); - } - }); - - describe("retryDecider init", () => { - it("sets defaultRetryDecider if options is undefined", () => { - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - expect(retryStrategy["retryDecider"]).toBe(defaultRetryDecider); - }); - - it("sets defaultRetryDecider if options.retryDecider is undefined", () => { - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts), {}); - expect(retryStrategy["retryDecider"]).toBe(defaultRetryDecider); - }); - - it("sets options.retryDecider if defined", () => { - const retryDecider = jest.fn(); - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts), { - retryDecider, - }); - expect(retryStrategy["retryDecider"]).toBe(retryDecider); - }); - }); - - describe("delayDecider init", () => { - it("sets defaultDelayDecider if options is undefined", () => { - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - expect(retryStrategy["delayDecider"]).toBe(defaultDelayDecider); - }); - - it("sets defaultDelayDecider if options.delayDecider undefined", () => { - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts), {}); - expect(retryStrategy["delayDecider"]).toBe(defaultDelayDecider); - }); - - it("sets options.delayDecider if defined", () => { - const delayDecider = jest.fn(); - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts), { - delayDecider, - }); - expect(retryStrategy["delayDecider"]).toBe(delayDecider); - }); - }); - - describe("retryQuota init", () => { - it("sets getDefaultRetryQuota if options is undefined", () => { - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - expect(retryStrategy["retryQuota"]).toBe(getDefaultRetryQuota(INITIAL_RETRY_TOKENS)); - }); - - it("sets getDefaultRetryQuota if options.delayDecider undefined", () => { - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts), {}); - expect(retryStrategy["retryQuota"]).toBe(getDefaultRetryQuota(INITIAL_RETRY_TOKENS)); - }); - - it("sets options.retryQuota if defined", () => { - const retryQuota = {} as RetryQuota; - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts), { - retryQuota, - }); - expect(retryStrategy["retryQuota"]).toBe(retryQuota); - }); - }); - - describe("delayDecider", () => { - describe("delayBase value passed", () => { - const testDelayBasePassed = async (delayBaseToTest: number, mockThrottlingError: boolean) => { - (isThrottlingError as jest.Mock).mockReturnValueOnce(mockThrottlingError); - - const mockError = new Error(); - await mockSuccessAfterOneFail(maxAttempts, { mockError }); - - expect(isThrottlingError as jest.Mock).toHaveBeenCalledTimes(1); - expect(isThrottlingError as jest.Mock).toHaveBeenCalledWith(mockError); - expect(defaultDelayDecider as jest.Mock).toHaveBeenCalledTimes(1); - expect((defaultDelayDecider as jest.Mock).mock.calls[0][0]).toBe(delayBaseToTest); - }; - - it("should be equal to THROTTLING_RETRY_DELAY_BASE if error is throttling error", async () => { - return testDelayBasePassed(THROTTLING_RETRY_DELAY_BASE, true); - }); - - it("should be equal to DEFAULT_RETRY_DELAY_BASE in error is not a throttling error", async () => { - return testDelayBasePassed(DEFAULT_RETRY_DELAY_BASE, false); - }); - }); - - describe("attempts value passed", () => { - it("on successful operation", async () => { - await mockSuccessfulOperation(maxAttempts); - expect(defaultDelayDecider as jest.Mock).not.toHaveBeenCalled(); - }); - - it("in case of single failure", async () => { - await mockSuccessAfterOneFail(maxAttempts); - expect(defaultDelayDecider as jest.Mock).toHaveBeenCalledTimes(1); - expect((defaultDelayDecider as jest.Mock).mock.calls[0][1]).toBe(1); - }); - - it("on all fails", async () => { - await mockFailedOperation(maxAttempts); - expect(defaultDelayDecider as jest.Mock).toHaveBeenCalledTimes(2); - expect((defaultDelayDecider as jest.Mock).mock.calls[0][1]).toBe(1); - expect((defaultDelayDecider as jest.Mock).mock.calls[1][1]).toBe(2); - }); - }); - - describe("totalRetryDelay", () => { - describe("when retry-after is not set", () => { - it("should be equal to sum of values computed by delayDecider", async () => { - jest.spyOn(global, "setTimeout"); - - const FIRST_DELAY = 100; - const SECOND_DELAY = 200; - - (defaultDelayDecider as jest.Mock).mockReturnValueOnce(FIRST_DELAY).mockReturnValueOnce(SECOND_DELAY); - - const maxAttempts = 3; - const error = await mockFailedOperation(maxAttempts); - expect(error.$metadata.totalRetryDelay).toEqual(FIRST_DELAY + SECOND_DELAY); - - expect(defaultDelayDecider as jest.Mock).toHaveBeenCalledTimes(maxAttempts - 1); - expect(setTimeout).toHaveBeenCalledTimes(maxAttempts - 1); - expect((setTimeout as unknown as jest.Mock).mock.calls[0][1]).toBe(FIRST_DELAY); - expect((setTimeout as unknown as jest.Mock).mock.calls[1][1]).toBe(SECOND_DELAY); - }); - }); - - describe("when retry-after is set", () => { - const getErrorWithValues = async ( - delayDeciderInMs: number, - retryAfter: number | string, - retryAfterHeaderName?: string - ) => { - (defaultDelayDecider as jest.Mock).mockReturnValueOnce(delayDeciderInMs); - - const maxAttempts = 2; - const mockError = new Error(); - Object.defineProperty(mockError, "$response", { - value: { - headers: { [retryAfterHeaderName ? retryAfterHeaderName : "retry-after"]: String(retryAfter) }, - }, - }); - const error = await mockFailedOperation(maxAttempts, { mockError }); - expect(defaultDelayDecider as jest.Mock).toHaveBeenCalledTimes(maxAttempts - 1); - expect(setTimeout).toHaveBeenCalledTimes(maxAttempts - 1); - - return error; - }; - - beforeEach(() => { - jest.spyOn(global, "setTimeout"); - }); - - describe("uses retry-after value if it's greater than that from delayDecider", () => { - beforeEach(() => { - const { isInstance } = HttpResponse; - (isInstance as unknown as jest.Mock).mockReturnValueOnce(true); - }); - - describe("when value is in seconds", () => { - const testWithHeaderName = async (retryAfterHeaderName: string) => { - const delayDeciderInMs = 2000; - const retryAfterInSeconds = 3; - - const error = await getErrorWithValues(delayDeciderInMs, retryAfterInSeconds, retryAfterHeaderName); - expect(error.$metadata.totalRetryDelay).toEqual(retryAfterInSeconds * 1000); - expect((setTimeout as unknown as jest.Mock).mock.calls[0][1]).toBe(retryAfterInSeconds * 1000); - }; - - it("with header in small case", async () => { - testWithHeaderName("retry-after"); - }); - - it("with header with first letter capital", async () => { - testWithHeaderName("Retry-After"); - }); - }); - - it("when value is a Date", async () => { - const mockDateNow = Date.now(); - jest.spyOn(Date, "now").mockReturnValue(mockDateNow); - - const delayDeciderInMs = 2000; - const retryAfterInSeconds = 3; - const retryAfterDate = new Date(mockDateNow + retryAfterInSeconds * 1000); - - const error = await getErrorWithValues(delayDeciderInMs, retryAfterDate.toISOString()); - expect(error.$metadata.totalRetryDelay).toEqual(retryAfterInSeconds * 1000); - expect((setTimeout as unknown as jest.Mock).mock.calls[0][1]).toBe(retryAfterInSeconds * 1000); - }); - }); - - it("ignores retry-after value if it's smaller than that from delayDecider", async () => { - const delayDeciderInMs = 3000; - const retryAfterInSeconds = 2; - - const error = await getErrorWithValues(delayDeciderInMs, retryAfterInSeconds); - expect(error.$metadata.totalRetryDelay).toEqual(delayDeciderInMs); - expect((setTimeout as unknown as jest.Mock).mock.calls[0][1]).toBe(delayDeciderInMs); - }); - }); - }); - }); - - describe("retryQuota", () => { - describe("hasRetryTokens", () => { - it("not called on successful operation", async () => { - const { hasRetryTokens } = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - await mockSuccessfulOperation(maxAttempts); - expect(hasRetryTokens).not.toHaveBeenCalled(); - }); - - it("called once in case of single failure", async () => { - const { hasRetryTokens } = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - await mockSuccessAfterOneFail(maxAttempts); - expect(hasRetryTokens).toHaveBeenCalledTimes(1); - }); - - it("called once on each retry request", async () => { - const { hasRetryTokens } = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - await mockFailedOperation(maxAttempts); - expect(hasRetryTokens).toHaveBeenCalledTimes(maxAttempts - 1); - }); - }); - - describe("releaseRetryTokens", () => { - it("called once without param on successful operation", async () => { - const { releaseRetryTokens } = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - await mockSuccessfulOperation(maxAttempts); - expect(releaseRetryTokens).toHaveBeenCalledTimes(1); - expect(releaseRetryTokens).toHaveBeenCalledWith(undefined); - }); - - it("called once with retryTokenAmount in case of single failure", async () => { - const retryTokens = 15; - const { releaseRetryTokens, retrieveRetryTokens } = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - (retrieveRetryTokens as jest.Mock).mockReturnValueOnce(retryTokens); - - await mockSuccessAfterOneFail(maxAttempts); - expect(releaseRetryTokens).toHaveBeenCalledTimes(1); - expect(releaseRetryTokens).toHaveBeenCalledWith(retryTokens); - }); - - it("called once with second retryTokenAmount in case of two failures", async () => { - const retryTokensFirst = 15; - const retryTokensSecond = 30; - - const { releaseRetryTokens, retrieveRetryTokens } = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - - (retrieveRetryTokens as jest.Mock).mockReturnValueOnce(retryTokensFirst).mockReturnValueOnce(retryTokensSecond); - - await mockSuccessAfterTwoFails(maxAttempts); - expect(releaseRetryTokens).toHaveBeenCalledTimes(1); - expect(releaseRetryTokens).toHaveBeenCalledWith(retryTokensSecond); - }); - - it("not called on unsuccessful operation", async () => { - const { releaseRetryTokens } = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - await mockFailedOperation(maxAttempts); - expect(releaseRetryTokens).not.toHaveBeenCalled(); - }); - }); - - describe("retrieveRetryTokens", () => { - it("not called on successful operation", async () => { - const { retrieveRetryTokens } = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - await mockSuccessfulOperation(maxAttempts); - expect(retrieveRetryTokens).not.toHaveBeenCalled(); - }); - - it("called once in case of single failure", async () => { - const { retrieveRetryTokens } = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - await mockSuccessAfterOneFail(maxAttempts); - expect(retrieveRetryTokens).toHaveBeenCalledTimes(1); - }); - - it("called once on each retry request", async () => { - const { retrieveRetryTokens } = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - await mockFailedOperation(maxAttempts); - expect(retrieveRetryTokens).toHaveBeenCalledTimes(maxAttempts - 1); - }); - }); - }); - - describe("should not retry", () => { - it("when the handler completes successfully", async () => { - const mockResponse = "mockResponse"; - const { response, output } = await mockSuccessfulOperation(maxAttempts, { - mockResponse, - }); - - expect(response).toStrictEqual(mockResponse); - expect(output.$metadata.attempts).toBe(1); - expect(output.$metadata.totalRetryDelay).toBe(0); - expect(defaultRetryDecider as jest.Mock).not.toHaveBeenCalled(); - expect(defaultDelayDecider as jest.Mock).not.toHaveBeenCalled(); - }); - - it("when retryDecider returns false", async () => { - (defaultRetryDecider as jest.Mock).mockReturnValueOnce(false); - const mockError = new Error(); - await mockFailedOperation(maxAttempts, { mockError }); - expect(defaultRetryDecider as jest.Mock).toHaveBeenCalledTimes(1); - expect(defaultRetryDecider as jest.Mock).toHaveBeenCalledWith(mockError); - }); - - it("when the maximum number of attempts is reached", async () => { - await mockFailedOperation(maxAttempts); - expect(defaultRetryDecider as jest.Mock).toHaveBeenCalledTimes(maxAttempts - 1); - }); - - describe("when retryQuota.hasRetryTokens returns false", () => { - it("in the first request", async () => { - const { hasRetryTokens, retrieveRetryTokens, releaseRetryTokens } = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - (hasRetryTokens as jest.Mock).mockReturnValueOnce(false); - - const mockError = new Error(); - await mockFailedOperation(maxAttempts, { mockError }); - - expect(hasRetryTokens).toHaveBeenCalledTimes(1); - expect(hasRetryTokens).toHaveBeenCalledWith(mockError); - expect(retrieveRetryTokens).not.toHaveBeenCalled(); - expect(releaseRetryTokens).not.toHaveBeenCalled(); - }); - - it("after the first retry", async () => { - const { hasRetryTokens, retrieveRetryTokens, releaseRetryTokens } = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - (hasRetryTokens as jest.Mock).mockReturnValueOnce(true).mockReturnValueOnce(false); - - const mockError = new Error(); - await mockFailedOperation(maxAttempts, { mockError }); - - expect(hasRetryTokens).toHaveBeenCalledTimes(2); - [1, 2].forEach((n) => { - expect(hasRetryTokens).toHaveBeenNthCalledWith(n, mockError); - }); - expect(retrieveRetryTokens).toHaveBeenCalledTimes(1); - expect(retrieveRetryTokens).toHaveBeenCalledWith(mockError); - expect(releaseRetryTokens).not.toHaveBeenCalled(); - }); - }); - }); - - describe("retry informational header: amz-sdk-invocation-id", () => { - describe("not added if HttpRequest.isInstance returns false", () => { - it("on successful operation", async () => { - await mockSuccessfulOperation(maxAttempts); - expect(next).toHaveBeenCalledTimes(1); - expect(next.mock.calls[0][0].request.headers["amz-sdk-invocation-id"]).not.toBeDefined(); - }); - - it("in case of single failure", async () => { - await mockSuccessAfterOneFail(maxAttempts); - expect(next).toHaveBeenCalledTimes(2); - [0, 1].forEach((index) => { - expect(next.mock.calls[index][0].request.headers["amz-sdk-invocation-id"]).not.toBeDefined(); - }); - }); - - it("in case of all failures", async () => { - await mockFailedOperation(maxAttempts); - expect(next).toHaveBeenCalledTimes(maxAttempts); - [...Array(maxAttempts).keys()].forEach((index) => { - expect(next.mock.calls[index][0].request.headers["amz-sdk-invocation-id"]).not.toBeDefined(); - }); - }); - }); - - it("uses a unique header for every SDK operation invocation", async () => { - const { isInstance } = HttpRequest; - (isInstance as unknown as jest.Mock).mockReturnValue(true); - - const uuidForInvocationOne = "uuid-invocation-1"; - const uuidForInvocationTwo = "uuid-invocation-2"; - (v4 as jest.Mock).mockReturnValueOnce(uuidForInvocationOne).mockReturnValueOnce(uuidForInvocationTwo); - - const next = jest.fn().mockResolvedValue({ - response: "mockResponse", - output: { $metadata: {} }, - }); - - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - await retryStrategy.retry(next, { request: { headers: {} } } as any); - await retryStrategy.retry(next, { request: { headers: {} } } as any); - - expect(next).toHaveBeenCalledTimes(2); - expect(next.mock.calls[0][0].request.headers["amz-sdk-invocation-id"]).toBe(uuidForInvocationOne); - expect(next.mock.calls[1][0].request.headers["amz-sdk-invocation-id"]).toBe(uuidForInvocationTwo); - - (isInstance as unknown as jest.Mock).mockReturnValue(false); - }); - - it("uses same value for additional HTTP requests associated with an SDK operation", async () => { - const { isInstance } = HttpRequest; - (isInstance as unknown as jest.Mock).mockReturnValueOnce(true); - - const uuidForInvocation = "uuid-invocation-1"; - (v4 as jest.Mock).mockReturnValueOnce(uuidForInvocation); - - await mockSuccessAfterOneFail(maxAttempts); - - expect(next).toHaveBeenCalledTimes(2); - expect(next.mock.calls[0][0].request.headers["amz-sdk-invocation-id"]).toBe(uuidForInvocation); - expect(next.mock.calls[1][0].request.headers["amz-sdk-invocation-id"]).toBe(uuidForInvocation); - - (isInstance as unknown as jest.Mock).mockReturnValue(false); - }); - }); - - describe("retry informational header: amz-sdk-request", () => { - describe("not added if HttpRequest.isInstance returns false", () => { - it("on successful operation", async () => { - await mockSuccessfulOperation(maxAttempts); - expect(next).toHaveBeenCalledTimes(1); - expect(next.mock.calls[0][0].request.headers["amz-sdk-request"]).not.toBeDefined(); - }); - - it("in case of single failure", async () => { - await mockSuccessAfterOneFail(maxAttempts); - expect(next).toHaveBeenCalledTimes(2); - [0, 1].forEach((index) => { - expect(next.mock.calls[index][0].request.headers["amz-sdk-request"]).not.toBeDefined(); - }); - }); - - it("in case of all failures", async () => { - await mockFailedOperation(maxAttempts); - expect(next).toHaveBeenCalledTimes(maxAttempts); - [...Array(maxAttempts).keys()].forEach((index) => { - expect(next.mock.calls[index][0].request.headers["amz-sdk-request"]).not.toBeDefined(); - }); - }); - }); - - it("adds header for each attempt", async () => { - const { isInstance } = HttpRequest; - (isInstance as unknown as jest.Mock).mockReturnValue(true); - - const mockError = new Error("mockError"); - next = jest.fn((args) => { - // the header needs to be verified inside jest.Mock as arguments in - // jest.mocks.calls has the value passed in final call - const index = next.mock.calls.length - 1; - expect(args.request.headers["amz-sdk-request"]).toBe(`attempt=${index + 1}; max=${maxAttempts}`); - throw mockError; - }); - - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - try { - await retryStrategy.retry(next, { request: { headers: {} } } as any); - } catch (error) { - expect(error).toStrictEqual(mockError); - return error; - } - - expect(next).toHaveBeenCalledTimes(maxAttempts); - (isInstance as unknown as jest.Mock).mockReturnValue(false); - }); - }); - - describe("defaults maxAttempts to DEFAULT_MAX_ATTEMPTS", () => { - it("when maxAttemptsProvider throws error", async () => { - const { isInstance } = HttpRequest; - (isInstance as unknown as jest.Mock).mockReturnValue(true); - - next = jest.fn((args) => { - expect(args.request.headers["amz-sdk-request"]).toBe(`attempt=1; max=${DEFAULT_MAX_ATTEMPTS}`); - return Promise.resolve({ - response: "mockResponse", - output: { $metadata: {} }, - }); - }); - - const retryStrategy = new StandardRetryStrategy(() => Promise.reject("ERROR")); - await retryStrategy.retry(next, { request: { headers: {} } } as any); - - expect(next).toHaveBeenCalledTimes(1); - (isInstance as unknown as jest.Mock).mockReturnValue(false); - }); - }); -}); diff --git a/packages/middleware-retry/src/StandardRetryStrategy.ts b/packages/middleware-retry/src/StandardRetryStrategy.ts deleted file mode 100644 index 3bcde9a58aa1..000000000000 --- a/packages/middleware-retry/src/StandardRetryStrategy.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { HttpRequest, HttpResponse } from "@aws-sdk/protocol-http"; -import { isThrottlingError } from "@aws-sdk/service-error-classification"; -import { SdkError } from "@aws-sdk/types"; -import { FinalizeHandler, FinalizeHandlerArguments, MetadataBearer, Provider, RetryStrategy } from "@aws-sdk/types"; -import { - DEFAULT_MAX_ATTEMPTS, - DEFAULT_RETRY_DELAY_BASE, - INITIAL_RETRY_TOKENS, - INVOCATION_ID_HEADER, - REQUEST_HEADER, - RETRY_MODES, - THROTTLING_RETRY_DELAY_BASE, -} from "@aws-sdk/util-retry"; -import { v4 } from "uuid"; - -import { getDefaultRetryQuota } from "./defaultRetryQuota"; -import { defaultDelayDecider } from "./delayDecider"; -import { defaultRetryDecider } from "./retryDecider"; -import { DelayDecider, RetryDecider, RetryQuota } from "./types"; -import { asSdkError } from "./util"; - -/** - * Strategy options to be passed to StandardRetryStrategy - */ -export interface StandardRetryStrategyOptions { - retryDecider?: RetryDecider; - delayDecider?: DelayDecider; - retryQuota?: RetryQuota; -} - -/** - * @deprecated use StandardRetryStrategy from @aws-sdk/util-retry - */ -export class StandardRetryStrategy implements RetryStrategy { - private retryDecider: RetryDecider; - private delayDecider: DelayDecider; - private retryQuota: RetryQuota; - public mode: string = RETRY_MODES.STANDARD; - - constructor(private readonly maxAttemptsProvider: Provider, options?: StandardRetryStrategyOptions) { - this.retryDecider = options?.retryDecider ?? defaultRetryDecider; - this.delayDecider = options?.delayDecider ?? defaultDelayDecider; - this.retryQuota = options?.retryQuota ?? getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - } - - private shouldRetry(error: SdkError, attempts: number, maxAttempts: number) { - return attempts < maxAttempts && this.retryDecider(error) && this.retryQuota.hasRetryTokens(error); - } - - private async getMaxAttempts() { - let maxAttempts: number; - try { - maxAttempts = await this.maxAttemptsProvider(); - } catch (error) { - maxAttempts = DEFAULT_MAX_ATTEMPTS; - } - return maxAttempts; - } - - async retry( - next: FinalizeHandler, - args: FinalizeHandlerArguments, - options?: { - beforeRequest: Function; - afterRequest: Function; - } - ) { - let retryTokenAmount; - let attempts = 0; - let totalDelay = 0; - - const maxAttempts = await this.getMaxAttempts(); - - const { request } = args; - if (HttpRequest.isInstance(request)) { - request.headers[INVOCATION_ID_HEADER] = v4(); - } - - while (true) { - try { - if (HttpRequest.isInstance(request)) { - request.headers[REQUEST_HEADER] = `attempt=${attempts + 1}; max=${maxAttempts}`; - } - - if (options?.beforeRequest) { - await options.beforeRequest(); - } - const { response, output } = await next(args); - if (options?.afterRequest) { - options.afterRequest(response); - } - - this.retryQuota.releaseRetryTokens(retryTokenAmount); - output.$metadata.attempts = attempts + 1; - output.$metadata.totalRetryDelay = totalDelay; - - return { response, output }; - } catch (e) { - const err = asSdkError(e); - attempts++; - if (this.shouldRetry(err as SdkError, attempts, maxAttempts)) { - retryTokenAmount = this.retryQuota.retrieveRetryTokens(err); - const delayFromDecider = this.delayDecider( - isThrottlingError(err) ? THROTTLING_RETRY_DELAY_BASE : DEFAULT_RETRY_DELAY_BASE, - attempts - ); - - const delayFromResponse = getDelayFromRetryAfterHeader(err.$response); - const delay = Math.max(delayFromResponse || 0, delayFromDecider); - - totalDelay += delay; - - await new Promise((resolve) => setTimeout(resolve, delay)); - continue; - } - - if (!err.$metadata) { - err.$metadata = {}; - } - - err.$metadata.attempts = attempts; - err.$metadata.totalRetryDelay = totalDelay; - throw err; - } - } - } -} - -/** - * Returns number of milliseconds to wait based on "Retry-After" header value. - */ -const getDelayFromRetryAfterHeader = (response: unknown): number | undefined => { - if (!HttpResponse.isInstance(response)) return; - - const retryAfterHeaderName = Object.keys(response.headers).find((key) => key.toLowerCase() === "retry-after"); - if (!retryAfterHeaderName) return; - const retryAfter = response.headers[retryAfterHeaderName]; - - const retryAfterSeconds = Number(retryAfter); - if (!Number.isNaN(retryAfterSeconds)) return retryAfterSeconds * 1000; - - const retryAfterDate = new Date(retryAfter); - return retryAfterDate.getTime() - Date.now(); -}; diff --git a/packages/middleware-retry/src/configurations.spec.ts b/packages/middleware-retry/src/configurations.spec.ts deleted file mode 100644 index b7a8224b7132..000000000000 --- a/packages/middleware-retry/src/configurations.spec.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { normalizeProvider } from "@aws-sdk/util-middleware"; -import { AdaptiveRetryStrategy, DEFAULT_MAX_ATTEMPTS, StandardRetryStrategy } from "@aws-sdk/util-retry"; - -import { - CONFIG_MAX_ATTEMPTS, - ENV_MAX_ATTEMPTS, - NODE_MAX_ATTEMPT_CONFIG_OPTIONS, - resolveRetryConfig, -} from "./configurations"; - -jest.mock("@aws-sdk/util-middleware"); -jest.mock("@aws-sdk/util-retry"); - -describe(resolveRetryConfig.name, () => { - const retryMode = jest.fn() as any; - - beforeEach(() => { - (normalizeProvider as jest.Mock).mockImplementation((input) => - typeof input === "function" ? input : () => Promise.resolve(input) - ); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("maxAttempts", () => { - it.each([1, 2, 3])("assigns provided value %s", async (maxAttempts) => { - const output = await resolveRetryConfig({ maxAttempts, retryMode }).maxAttempts(); - expect(output).toStrictEqual(maxAttempts); - }); - - it(`assigns default ${DEFAULT_MAX_ATTEMPTS} is value not provided`, async () => { - const output = await resolveRetryConfig({ retryMode }).maxAttempts(); - expect(output).toStrictEqual(DEFAULT_MAX_ATTEMPTS); - }); - }); - - describe("retryStrategy", () => { - it("passes retryStrategy if present", () => { - const mockRetryStrategy = { - retry: jest.fn(), - }; - const { retryStrategy } = resolveRetryConfig({ - retryMode, - retryStrategy: mockRetryStrategy, - }); - expect(retryStrategy()).resolves.toEqual(mockRetryStrategy); - }); - - describe("creates RetryStrategy if retryStrategy not present", () => { - describe("StandardRetryStrategy", () => { - describe("when retryMode=standard", () => { - describe("passes maxAttempts if present", () => { - const retryMode = "standard"; - for (const maxAttempts of [1, 2, 3]) { - it(`when maxAttempts=${maxAttempts}`, async () => { - const { retryStrategy } = resolveRetryConfig({ maxAttempts, retryMode }); - await retryStrategy(); - expect(StandardRetryStrategy as jest.Mock).toHaveBeenCalledTimes(1); - expect(AdaptiveRetryStrategy as jest.Mock).not.toHaveBeenCalled(); - const output = await (StandardRetryStrategy as jest.Mock).mock.calls[0][0](); - expect(output).toStrictEqual(maxAttempts); - }); - } - }); - }); - - describe("when retryMode returns 'standard'", () => { - describe("passes maxAttempts if present", () => { - beforeEach(() => { - retryMode.mockResolvedValueOnce("standard"); - }); - for (const maxAttempts of [1, 2, 3]) { - it(`when maxAttempts=${maxAttempts}`, async () => { - const { retryStrategy } = resolveRetryConfig({ maxAttempts, retryMode }); - await retryStrategy(); - expect(retryMode).toHaveBeenCalledTimes(1); - expect(StandardRetryStrategy as jest.Mock).toHaveBeenCalledTimes(1); - expect(AdaptiveRetryStrategy as jest.Mock).not.toHaveBeenCalled(); - const output = await (StandardRetryStrategy as jest.Mock).mock.calls[0][0](); - expect(output).toStrictEqual(maxAttempts); - }); - } - }); - }); - }); - - describe("AdaptiveRetryStrategy", () => { - describe("when retryMode=adaptive", () => { - describe("passes maxAttempts if present", () => { - const retryMode = "adaptive"; - for (const maxAttempts of [1, 2, 3]) { - it(`when maxAttempts=${maxAttempts}`, async () => { - const { retryStrategy } = resolveRetryConfig({ maxAttempts, retryMode }); - await retryStrategy(); - expect(StandardRetryStrategy as jest.Mock).not.toHaveBeenCalled(); - expect(AdaptiveRetryStrategy as jest.Mock).toHaveBeenCalledTimes(1); - const output = await (AdaptiveRetryStrategy as jest.Mock).mock.calls[0][0](); - expect(output).toStrictEqual(maxAttempts); - }); - } - }); - }); - - describe("when retryMode returns 'adaptive'", () => { - describe("passes maxAttempts if present", () => { - beforeEach(() => { - retryMode.mockResolvedValueOnce("adaptive"); - }); - for (const maxAttempts of [1, 2, 3]) { - it(`when maxAttempts=${maxAttempts}`, async () => { - const { retryStrategy } = resolveRetryConfig({ maxAttempts, retryMode }); - await retryStrategy(); - expect(retryMode).toHaveBeenCalledTimes(1); - expect(StandardRetryStrategy as jest.Mock).not.toHaveBeenCalled(); - expect(AdaptiveRetryStrategy as jest.Mock).toHaveBeenCalledTimes(1); - const output = await (AdaptiveRetryStrategy as jest.Mock).mock.calls[0][0](); - expect(output).toStrictEqual(maxAttempts); - }); - } - }); - }); - }); - }); - }); - - describe("node maxAttempts config options", () => { - describe("environmentVariableSelector", () => { - it(`should return value of env ${ENV_MAX_ATTEMPTS} is number`, () => { - const value = "3"; - const env = { [ENV_MAX_ATTEMPTS]: value }; - expect(NODE_MAX_ATTEMPT_CONFIG_OPTIONS.environmentVariableSelector(env)).toBe(parseInt(value)); - }); - - it(`should return undefined if env ${ENV_MAX_ATTEMPTS} is not set`, () => { - expect(NODE_MAX_ATTEMPT_CONFIG_OPTIONS.environmentVariableSelector({})).toBe(undefined); - }); - - it(`should throw if if value of env ${ENV_MAX_ATTEMPTS} is not a number`, () => { - const value = "not a number"; - const env = { [ENV_MAX_ATTEMPTS]: value }; - expect(() => NODE_MAX_ATTEMPT_CONFIG_OPTIONS.environmentVariableSelector(env)).toThrow(""); - }); - }); - - describe("configFileSelector", () => { - it(`should return value of shared INI files entry ${CONFIG_MAX_ATTEMPTS} is number`, () => { - const value = "3"; - const profile = { [CONFIG_MAX_ATTEMPTS]: value }; - expect(NODE_MAX_ATTEMPT_CONFIG_OPTIONS.configFileSelector(profile)).toBe(parseInt(value)); - }); - - it(`should return undefined if shared INI files entry ${CONFIG_MAX_ATTEMPTS} is not set`, () => { - expect(NODE_MAX_ATTEMPT_CONFIG_OPTIONS.configFileSelector({})).toBe(undefined); - }); - - it(`should throw if shared INI files entry ${CONFIG_MAX_ATTEMPTS} is not a number`, () => { - const value = "not a number"; - const profile = { [CONFIG_MAX_ATTEMPTS]: value }; - expect(() => NODE_MAX_ATTEMPT_CONFIG_OPTIONS.configFileSelector(profile)).toThrow(""); - }); - }); - - describe("default", () => { - it(`should equal to ${DEFAULT_MAX_ATTEMPTS}`, () => { - expect(NODE_MAX_ATTEMPT_CONFIG_OPTIONS.default).toBe(DEFAULT_MAX_ATTEMPTS); - }); - }); - }); -}); diff --git a/packages/middleware-retry/src/configurations.ts b/packages/middleware-retry/src/configurations.ts deleted file mode 100644 index 7d8672d59fbf..000000000000 --- a/packages/middleware-retry/src/configurations.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { LoadedConfigSelectors } from "@aws-sdk/node-config-provider"; -import { Provider, RetryStrategy, RetryStrategyV2 } from "@aws-sdk/types"; -import { normalizeProvider } from "@aws-sdk/util-middleware"; -import { - AdaptiveRetryStrategy, - DEFAULT_MAX_ATTEMPTS, - DEFAULT_RETRY_MODE, - RETRY_MODES, - StandardRetryStrategy, -} from "@aws-sdk/util-retry"; - -export const ENV_MAX_ATTEMPTS = "AWS_MAX_ATTEMPTS"; -export const CONFIG_MAX_ATTEMPTS = "max_attempts"; - -export const NODE_MAX_ATTEMPT_CONFIG_OPTIONS: LoadedConfigSelectors = { - environmentVariableSelector: (env) => { - const value = env[ENV_MAX_ATTEMPTS]; - if (!value) return undefined; - const maxAttempt = parseInt(value); - if (Number.isNaN(maxAttempt)) { - throw new Error(`Environment variable ${ENV_MAX_ATTEMPTS} mast be a number, got "${value}"`); - } - return maxAttempt; - }, - configFileSelector: (profile) => { - const value = profile[CONFIG_MAX_ATTEMPTS]; - if (!value) return undefined; - const maxAttempt = parseInt(value); - if (Number.isNaN(maxAttempt)) { - throw new Error(`Shared config file entry ${CONFIG_MAX_ATTEMPTS} mast be a number, got "${value}"`); - } - return maxAttempt; - }, - default: DEFAULT_MAX_ATTEMPTS, -}; - -export interface RetryInputConfig { - /** - * The maximum number of times requests that encounter retryable failures should be attempted. - */ - maxAttempts?: number | Provider; - /** - * The strategy to retry the request. Using built-in exponential backoff strategy by default. - */ - retryStrategy?: RetryStrategy | RetryStrategyV2; -} - -interface PreviouslyResolved { - /** - * Specifies provider for retry algorithm to use. - * @internal - */ - retryMode: string | Provider; -} - -export interface RetryResolvedConfig { - /** - * Resolved value for input config {@link RetryInputConfig.maxAttempts} - */ - maxAttempts: Provider; - /** - * Resolved value for input config {@link RetryInputConfig.retryStrategy} - */ - retryStrategy: Provider; -} - -export const resolveRetryConfig = (input: T & PreviouslyResolved & RetryInputConfig): T & RetryResolvedConfig => { - const { retryStrategy } = input; - const maxAttempts = normalizeProvider(input.maxAttempts ?? DEFAULT_MAX_ATTEMPTS); - return { - ...input, - maxAttempts, - retryStrategy: async () => { - if (retryStrategy) { - return retryStrategy; - } - const retryMode = await normalizeProvider(input.retryMode)(); - if (retryMode === RETRY_MODES.ADAPTIVE) { - return new AdaptiveRetryStrategy(maxAttempts); - } - return new StandardRetryStrategy(maxAttempts); - }, - }; -}; - -export const ENV_RETRY_MODE = "AWS_RETRY_MODE"; -export const CONFIG_RETRY_MODE = "retry_mode"; - -export const NODE_RETRY_MODE_CONFIG_OPTIONS: LoadedConfigSelectors = { - environmentVariableSelector: (env) => env[ENV_RETRY_MODE], - configFileSelector: (profile) => profile[CONFIG_RETRY_MODE], - default: DEFAULT_RETRY_MODE, -}; diff --git a/packages/middleware-retry/src/defaultRetryQuota.spec.ts b/packages/middleware-retry/src/defaultRetryQuota.spec.ts deleted file mode 100644 index eed0fb9b92ca..000000000000 --- a/packages/middleware-retry/src/defaultRetryQuota.spec.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { SdkError } from "@aws-sdk/types"; -import { INITIAL_RETRY_TOKENS, NO_RETRY_INCREMENT, RETRY_COST, TIMEOUT_RETRY_COST } from "@aws-sdk/util-retry"; - -import { getDefaultRetryQuota } from "./defaultRetryQuota"; - -describe("defaultRetryQuota", () => { - const getMockError = () => new Error() as SdkError; - const getMockTimeoutError = () => - Object.assign(new Error(), { - name: "TimeoutError", - }) as SdkError; - - const getDrainedRetryQuota = ( - targetCapacity: number, - error: SdkError, - initialRetryTokens: number = INITIAL_RETRY_TOKENS - ) => { - const retryQuota = getDefaultRetryQuota(initialRetryTokens); - let availableCapacity = initialRetryTokens; - while (availableCapacity >= targetCapacity) { - retryQuota.retrieveRetryTokens(error); - availableCapacity -= targetCapacity; - } - return retryQuota; - }; - - describe("custom initial retry tokens", () => { - it("hasRetryTokens returns false if capacity is not available", () => { - const customRetryTokens = 100; - const error = getMockError(); - const retryQuota = getDrainedRetryQuota(RETRY_COST, error, customRetryTokens); - expect(retryQuota.hasRetryTokens(error)).toBe(false); - }); - - it("retrieveRetryToken throws error if retry tokens not available", () => { - const customRetryTokens = 100; - const error = getMockError(); - const retryQuota = getDrainedRetryQuota(RETRY_COST, error, customRetryTokens); - expect(() => { - retryQuota.retrieveRetryTokens(error); - }).toThrowError(new Error("No retry token available")); - }); - }); - - describe("hasRetryTokens", () => { - describe("returns true if capacity is available", () => { - it("when it's TimeoutError", () => { - const timeoutError = getMockTimeoutError(); - expect(getDefaultRetryQuota(INITIAL_RETRY_TOKENS).hasRetryTokens(timeoutError)).toBe(true); - }); - - it("when it's not TimeoutError", () => { - expect(getDefaultRetryQuota(INITIAL_RETRY_TOKENS).hasRetryTokens(getMockError())).toBe(true); - }); - }); - - describe("returns false if capacity is not available", () => { - it("when it's TimeoutError", () => { - const timeoutError = getMockTimeoutError(); - const retryQuota = getDrainedRetryQuota(TIMEOUT_RETRY_COST, timeoutError); - expect(retryQuota.hasRetryTokens(timeoutError)).toBe(false); - }); - - it("when it's not TimeoutError", () => { - const error = getMockError(); - const retryQuota = getDrainedRetryQuota(RETRY_COST, error); - expect(retryQuota.hasRetryTokens(error)).toBe(false); - }); - }); - }); - - describe("retrieveRetryToken", () => { - describe("returns retry tokens amount if available", () => { - it("when it's TimeoutError", () => { - const timeoutError = getMockTimeoutError(); - expect(getDefaultRetryQuota(INITIAL_RETRY_TOKENS).retrieveRetryTokens(timeoutError)).toBe(TIMEOUT_RETRY_COST); - }); - - it("when it's not TimeoutError", () => { - expect(getDefaultRetryQuota(INITIAL_RETRY_TOKENS).retrieveRetryTokens(getMockError())).toBe(RETRY_COST); - }); - }); - - describe("throws error if retry tokens not available", () => { - it("when it's TimeoutError", () => { - const timeoutError = getMockTimeoutError(); - const retryQuota = getDrainedRetryQuota(TIMEOUT_RETRY_COST, timeoutError); - expect(() => { - retryQuota.retrieveRetryTokens(timeoutError); - }).toThrowError(new Error("No retry token available")); - }); - - it("when it's not TimeoutError", () => { - const error = getMockError(); - const retryQuota = getDrainedRetryQuota(RETRY_COST, error); - expect(() => { - retryQuota.retrieveRetryTokens(error); - }).toThrowError(new Error("No retry token available")); - }); - }); - }); - - describe("releaseRetryToken", () => { - it("adds capacityReleaseAmount if passed", () => { - const error = getMockError(); - const retryQuota = getDrainedRetryQuota(RETRY_COST, error); - - // Ensure that retry tokens are not available. - expect(retryQuota.hasRetryTokens(error)).toBe(false); - - // Release RETRY_COST tokens. - retryQuota.releaseRetryTokens(RETRY_COST); - expect(retryQuota.hasRetryTokens(error)).toBe(true); - expect(retryQuota.retrieveRetryTokens(error)).toBe(RETRY_COST); - expect(retryQuota.hasRetryTokens(error)).toBe(false); - }); - - it("adds NO_RETRY_INCREMENT if capacityReleaseAmount not passed", () => { - const error = getMockError(); - const retryQuota = getDrainedRetryQuota(RETRY_COST, error); - - // retry tokens will not be available till NO_RETRY_INCREMENT is added - // till it's equal to RETRY_COST - (INITIAL_RETRY_TOKENS % RETRY_COST) - let tokensReleased = 0; - const tokensToBeReleased = RETRY_COST - (INITIAL_RETRY_TOKENS % RETRY_COST); - while (tokensReleased < tokensToBeReleased) { - expect(retryQuota.hasRetryTokens(error)).toBe(false); - retryQuota.releaseRetryTokens(); - tokensReleased += NO_RETRY_INCREMENT; - } - expect(retryQuota.hasRetryTokens(error)).toBe(true); - }); - - it("ensures availableCapacity is maxed at INITIAL_RETRY_TOKENS", () => { - const error = getMockError(); - const retryQuota = getDefaultRetryQuota(INITIAL_RETRY_TOKENS); - - // release 100 tokens. - [...Array(100).keys()].forEach(() => { - retryQuota.releaseRetryTokens(); - }); - - // availableCapacity is still maxed at INITIAL_RETRY_TOKENS - // hasRetryTokens would be true only till INITIAL_RETRY_TOKENS/RETRY_COST times - [...Array(Math.floor(INITIAL_RETRY_TOKENS / RETRY_COST)).keys()].forEach(() => { - expect(retryQuota.hasRetryTokens(error)).toBe(true); - retryQuota.retrieveRetryTokens(error); - }); - expect(retryQuota.hasRetryTokens(error)).toBe(false); - }); - }); -}); diff --git a/packages/middleware-retry/src/defaultRetryQuota.ts b/packages/middleware-retry/src/defaultRetryQuota.ts deleted file mode 100644 index b8d1c804ca2e..000000000000 --- a/packages/middleware-retry/src/defaultRetryQuota.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { SdkError } from "@aws-sdk/types"; -import { NO_RETRY_INCREMENT, RETRY_COST, TIMEOUT_RETRY_COST } from "@aws-sdk/util-retry"; - -import { RetryQuota } from "./types"; - -export interface DefaultRetryQuotaOptions { - /** - * The total amount of retry token to be incremented from retry token balance - * if an SDK operation invocation succeeds without requiring a retry request. - */ - noRetryIncrement?: number; - - /** - * The total amount of retry tokens to be decremented from retry token balance. - */ - retryCost?: number; - - /** - * The total amount of retry tokens to be decremented from retry token balance - * when a throttling error is encountered. - */ - timeoutRetryCost?: number; -} - -export const getDefaultRetryQuota = (initialRetryTokens: number, options?: DefaultRetryQuotaOptions): RetryQuota => { - const MAX_CAPACITY = initialRetryTokens; - const noRetryIncrement = options?.noRetryIncrement ?? NO_RETRY_INCREMENT; - const retryCost = options?.retryCost ?? RETRY_COST; - const timeoutRetryCost = options?.timeoutRetryCost ?? TIMEOUT_RETRY_COST; - - let availableCapacity = initialRetryTokens; - - const getCapacityAmount = (error: SdkError) => (error.name === "TimeoutError" ? timeoutRetryCost : retryCost); - - const hasRetryTokens = (error: SdkError) => getCapacityAmount(error) <= availableCapacity; - - const retrieveRetryTokens = (error: SdkError) => { - if (!hasRetryTokens(error)) { - // retryStrategy should stop retrying, and return last error - throw new Error("No retry token available"); - } - const capacityAmount = getCapacityAmount(error); - availableCapacity -= capacityAmount; - return capacityAmount; - }; - - const releaseRetryTokens = (capacityReleaseAmount?: number) => { - availableCapacity += capacityReleaseAmount ?? noRetryIncrement; - availableCapacity = Math.min(availableCapacity, MAX_CAPACITY); - }; - - return Object.freeze({ - hasRetryTokens, - retrieveRetryTokens, - releaseRetryTokens, - }); -}; diff --git a/packages/middleware-retry/src/delayDecider.spec.ts b/packages/middleware-retry/src/delayDecider.spec.ts deleted file mode 100644 index 0457f9a19c93..000000000000 --- a/packages/middleware-retry/src/delayDecider.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { MAXIMUM_RETRY_DELAY } from "@aws-sdk/util-retry"; - -import { defaultDelayDecider } from "./delayDecider"; - -describe("defaultDelayDecider", () => { - const mathDotRandom = Math.random; - - beforeEach(() => { - Math.random = jest.fn().mockReturnValue(1); - }); - - afterEach(() => { - Math.random = mathDotRandom; - }); - - describe(`retry delay increases exponentially with attempt number`, () => { - [0, 1, 2, 3].forEach((attempts) => { - const mockDelayBase = 100; - const expectedDelay = Math.floor(2 ** attempts * mockDelayBase); - it(`(${mockDelayBase}, ${attempts}) returns ${expectedDelay}`, () => { - expect(defaultDelayDecider(mockDelayBase, attempts)).toBe(expectedDelay); - }); - }); - }); - - describe(`caps retry delay at ${MAXIMUM_RETRY_DELAY / 1000} seconds`, () => { - it("when value exceeded because of high delayBase", () => { - expect(defaultDelayDecider(MAXIMUM_RETRY_DELAY + 1, 0)).toBe(MAXIMUM_RETRY_DELAY); - expect(defaultDelayDecider(MAXIMUM_RETRY_DELAY + 2, 0)).toBe(MAXIMUM_RETRY_DELAY); - }); - - it("when value exceeded because of high attempts number", () => { - const largeAttemptsNumber = Math.ceil(Math.log2(MAXIMUM_RETRY_DELAY)); - expect(defaultDelayDecider(1, largeAttemptsNumber)).toBe(MAXIMUM_RETRY_DELAY); - expect(defaultDelayDecider(1, largeAttemptsNumber + 1)).toBe(MAXIMUM_RETRY_DELAY); - }); - }); - - describe("randomizes the retry delay value", () => { - Array.from({ length: 3 }, () => Math.random()).forEach((mockRandomValue) => { - const attempts = 0; - const delayBase = 100; - const expectedDelay = Math.floor(mockRandomValue * 2 ** attempts * delayBase); - it(`(${delayBase}, ${attempts}) with mock Math.random=${mockRandomValue} returns ${expectedDelay}`, () => { - Math.random = jest.fn().mockReturnValue(mockRandomValue); - expect(defaultDelayDecider(delayBase, attempts)).toBe(expectedDelay); - }); - }); - }); -}); diff --git a/packages/middleware-retry/src/delayDecider.ts b/packages/middleware-retry/src/delayDecider.ts deleted file mode 100644 index 0951fc57d8bd..000000000000 --- a/packages/middleware-retry/src/delayDecider.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { MAXIMUM_RETRY_DELAY } from "@aws-sdk/util-retry"; - -/** - * Calculate a capped, fully-jittered exponential backoff time. - */ -export const defaultDelayDecider = (delayBase: number, attempts: number) => - Math.floor(Math.min(MAXIMUM_RETRY_DELAY, Math.random() * 2 ** attempts * delayBase)); diff --git a/packages/middleware-retry/src/index.ts b/packages/middleware-retry/src/index.ts index 9ebe326af627..573392466758 100644 --- a/packages/middleware-retry/src/index.ts +++ b/packages/middleware-retry/src/index.ts @@ -1,7 +1 @@ -export * from "./AdaptiveRetryStrategy"; -export * from "./StandardRetryStrategy"; -export * from "./configurations"; -export * from "./delayDecider"; -export * from "./omitRetryHeadersMiddleware"; -export * from "./retryDecider"; -export * from "./retryMiddleware"; +export * from "@smithy/middleware-retry"; diff --git a/packages/middleware-retry/src/omitRetryHeadersMiddleware.spec.ts b/packages/middleware-retry/src/omitRetryHeadersMiddleware.spec.ts deleted file mode 100644 index 5acfc4e3fe15..000000000000 --- a/packages/middleware-retry/src/omitRetryHeadersMiddleware.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { HttpRequest } from "@aws-sdk/protocol-http"; -import { FinalizeHandlerArguments, MiddlewareStack } from "@aws-sdk/types"; -import { INVOCATION_ID_HEADER, REQUEST_HEADER } from "@aws-sdk/util-retry"; - -import { - getOmitRetryHeadersPlugin, - omitRetryHeadersMiddleware, - omitRetryHeadersMiddlewareOptions, -} from "./omitRetryHeadersMiddleware"; - -describe("getOmitRetryHeadersPlugin", () => { - const mockClientStack = { - add: jest.fn(), - addRelativeTo: jest.fn(), - }; - - afterEach(() => { - jest.clearAllMocks(); - }); - - it(`adds omitRetryHeadersMiddleware`, () => { - getOmitRetryHeadersPlugin({}).applyToStack(mockClientStack as unknown as MiddlewareStack); - expect(mockClientStack.addRelativeTo).toHaveBeenCalledTimes(1); - expect(mockClientStack.addRelativeTo.mock.calls[0][1]).toEqual(omitRetryHeadersMiddlewareOptions); - }); -}); - -describe("omitRetryHeadersMiddleware", () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it("remove retry headers", async () => { - const next = jest.fn(); - const args = { - request: new HttpRequest({ - headers: { - [INVOCATION_ID_HEADER]: "12345", - [REQUEST_HEADER]: "maxAttempts=30", - }, - }), - }; - - await omitRetryHeadersMiddleware()(next)(args as FinalizeHandlerArguments); - expect(next).toHaveBeenCalledTimes(1); - expect(next.mock.calls[0][0].request.headers[INVOCATION_ID_HEADER]).toBeUndefined(); - expect(next.mock.calls[0][0].request.headers[REQUEST_HEADER]).toBeUndefined(); - }); -}); diff --git a/packages/middleware-retry/src/omitRetryHeadersMiddleware.ts b/packages/middleware-retry/src/omitRetryHeadersMiddleware.ts deleted file mode 100644 index b6d78fec4489..000000000000 --- a/packages/middleware-retry/src/omitRetryHeadersMiddleware.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { HttpRequest } from "@aws-sdk/protocol-http"; -import { - FinalizeHandler, - FinalizeHandlerArguments, - FinalizeHandlerOutput, - MetadataBearer, - Pluggable, - RelativeMiddlewareOptions, -} from "@aws-sdk/types"; -import { INVOCATION_ID_HEADER, REQUEST_HEADER } from "@aws-sdk/util-retry"; - -export const omitRetryHeadersMiddleware = - () => - (next: FinalizeHandler): FinalizeHandler => - async (args: FinalizeHandlerArguments): Promise> => { - const { request } = args; - if (HttpRequest.isInstance(request)) { - delete request.headers[INVOCATION_ID_HEADER]; - delete request.headers[REQUEST_HEADER]; - } - return next(args); - }; - -export const omitRetryHeadersMiddlewareOptions: RelativeMiddlewareOptions = { - name: "omitRetryHeadersMiddleware", - tags: ["RETRY", "HEADERS", "OMIT_RETRY_HEADERS"], - relation: "before", - toMiddleware: "awsAuthMiddleware", - override: true, -}; - -export const getOmitRetryHeadersPlugin = (options: unknown): Pluggable => ({ - applyToStack: (clientStack) => { - clientStack.addRelativeTo(omitRetryHeadersMiddleware(), omitRetryHeadersMiddlewareOptions); - }, -}); diff --git a/packages/middleware-retry/src/retryDecider.spec.ts b/packages/middleware-retry/src/retryDecider.spec.ts deleted file mode 100644 index e48bc9541204..000000000000 --- a/packages/middleware-retry/src/retryDecider.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { - isClockSkewError, - isRetryableByTrait, - isThrottlingError, - isTransientError, -} from "@aws-sdk/service-error-classification"; -import { SdkError } from "@aws-sdk/types"; - -import { defaultRetryDecider } from "./retryDecider"; - -jest.mock("@aws-sdk/service-error-classification"); - -describe("defaultRetryDecider", () => { - const createMockError = () => Object.assign(new Error(), { $metadata: {} }) as SdkError; - - beforeEach(() => { - (isRetryableByTrait as jest.Mock).mockReturnValue(false); - (isClockSkewError as jest.Mock).mockReturnValue(false); - (isThrottlingError as jest.Mock).mockReturnValue(false); - (isTransientError as jest.Mock).mockReturnValue(false); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("should return false when the provided error is falsy", () => { - expect(defaultRetryDecider(null as any)).toBe(false); - expect(isRetryableByTrait as jest.Mock).toHaveBeenCalledTimes(0); - expect(isClockSkewError as jest.Mock).toHaveBeenCalledTimes(0); - expect(isThrottlingError as jest.Mock).toHaveBeenCalledTimes(0); - expect(isTransientError as jest.Mock).toHaveBeenCalledTimes(0); - }); - - it("should return true for RetryableByTrait error", () => { - (isRetryableByTrait as jest.Mock).mockReturnValueOnce(true); - expect(defaultRetryDecider(createMockError())).toBe(true); - expect(isRetryableByTrait as jest.Mock).toHaveBeenCalledTimes(1); - expect(isClockSkewError as jest.Mock).toHaveBeenCalledTimes(0); - expect(isThrottlingError as jest.Mock).toHaveBeenCalledTimes(0); - expect(isTransientError as jest.Mock).toHaveBeenCalledTimes(0); - }); - - it("should return true for ClockSkewError", () => { - (isClockSkewError as jest.Mock).mockReturnValueOnce(true); - expect(defaultRetryDecider(createMockError())).toBe(true); - expect(isRetryableByTrait as jest.Mock).toHaveBeenCalledTimes(1); - expect(isClockSkewError as jest.Mock).toHaveBeenCalledTimes(1); - expect(isThrottlingError as jest.Mock).toHaveBeenCalledTimes(0); - expect(isTransientError as jest.Mock).toHaveBeenCalledTimes(0); - }); - - it("should return true for ThrottlingError", () => { - (isThrottlingError as jest.Mock).mockReturnValueOnce(true); - expect(defaultRetryDecider(createMockError())).toBe(true); - expect(isRetryableByTrait as jest.Mock).toHaveBeenCalledTimes(1); - expect(isClockSkewError as jest.Mock).toHaveBeenCalledTimes(1); - expect(isThrottlingError as jest.Mock).toHaveBeenCalledTimes(1); - expect(isTransientError as jest.Mock).toHaveBeenCalledTimes(0); - }); - - it("should return true for TransientError", () => { - (isTransientError as jest.Mock).mockReturnValueOnce(true); - expect(defaultRetryDecider(createMockError())).toBe(true); - expect(isRetryableByTrait as jest.Mock).toHaveBeenCalledTimes(1); - expect(isClockSkewError as jest.Mock).toHaveBeenCalledTimes(1); - expect(isThrottlingError as jest.Mock).toHaveBeenCalledTimes(1); - expect(isTransientError as jest.Mock).toHaveBeenCalledTimes(1); - }); - - it("should return false for other errors", () => { - expect(defaultRetryDecider(createMockError())).toBe(false); - expect(isRetryableByTrait as jest.Mock).toHaveBeenCalledTimes(1); - expect(isClockSkewError as jest.Mock).toHaveBeenCalledTimes(1); - expect(isThrottlingError as jest.Mock).toHaveBeenCalledTimes(1); - expect(isTransientError as jest.Mock).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/middleware-retry/src/retryDecider.ts b/packages/middleware-retry/src/retryDecider.ts deleted file mode 100644 index c0a02194bf7c..000000000000 --- a/packages/middleware-retry/src/retryDecider.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - isClockSkewError, - isRetryableByTrait, - isThrottlingError, - isTransientError, -} from "@aws-sdk/service-error-classification"; -import { SdkError } from "@aws-sdk/types"; - -export const defaultRetryDecider = (error: SdkError) => { - if (!error) { - return false; - } - - return isRetryableByTrait(error) || isClockSkewError(error) || isThrottlingError(error) || isTransientError(error); -}; diff --git a/packages/middleware-retry/src/retryMiddleware.spec.ts b/packages/middleware-retry/src/retryMiddleware.spec.ts deleted file mode 100644 index 94a07200896e..000000000000 --- a/packages/middleware-retry/src/retryMiddleware.spec.ts +++ /dev/null @@ -1,418 +0,0 @@ -import { HttpRequest, HttpResponse } from "@aws-sdk/protocol-http"; -import { isServerError, isThrottlingError, isTransientError } from "@aws-sdk/service-error-classification"; -import { FinalizeHandlerArguments, HandlerExecutionContext, MiddlewareStack } from "@aws-sdk/types"; -import { INVOCATION_ID_HEADER, REQUEST_HEADER } from "@aws-sdk/util-retry"; -import { v4 } from "uuid"; - -import { getRetryPlugin, retryMiddleware, retryMiddlewareOptions } from "./retryMiddleware"; - -jest.mock("@aws-sdk/service-error-classification"); -jest.mock("@aws-sdk/protocol-http"); -jest.mock("uuid"); - -describe(getRetryPlugin.name, () => { - const mockClientStack = { - add: jest.fn(), - }; - const mockRetryStrategy = { - mode: "mock", - retry: jest.fn(), - }; - beforeEach(() => { - (isThrottlingError as jest.Mock).mockReturnValue(false); - (isTransientError as jest.Mock).mockReturnValue(false); - (isServerError as jest.Mock).mockReturnValue(false); - (HttpRequest as unknown as jest.Mock).mockReturnValue({ - isInstance: jest.fn().mockReturnValue(false), - }); - (HttpResponse as unknown as jest.Mock).mockReturnValue({ - isInstance: jest.fn().mockReturnValue(false), - }); - (v4 as jest.Mock).mockReturnValue("42"); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("adds retryMiddleware", () => { - [1, 2, 3].forEach((maxAttempts) => { - it(`when maxAttempts=${maxAttempts}`, () => { - getRetryPlugin({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue(mockRetryStrategy), - }).applyToStack(mockClientStack as unknown as MiddlewareStack); - expect(mockClientStack.add).toHaveBeenCalledTimes(1); - expect(mockClientStack.add.mock.calls[0][1]).toEqual(retryMiddlewareOptions); - }); - }); - }); -}); - -describe(retryMiddleware.name, () => { - const maxAttempts = 2; - afterEach(() => { - jest.clearAllMocks(); - }); - describe("RetryStrategy", () => { - const mockRetryStrategy = { - mode: "mock", - retry: jest.fn(), - }; - - it("calls retryStrategy.retry with next and args", async () => { - const next = jest.fn(); - const args = { - request: { headers: {} }, - }; - const context: HandlerExecutionContext = {}; - - await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(mockRetryStrategy.retry).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.retry).toHaveBeenCalledWith(next, args); - expect(context.userAgent).toContainEqual(["cfg/retry-mode", mockRetryStrategy.mode]); - }); - }); - - describe("RetryStrategyV2", () => { - const args = { - request: { headers: {} }, - }; - const partitionId = "test_partition_id"; - const context: HandlerExecutionContext = { - partition_id: partitionId, - }; - const mockRetryToken = { - getRetryToken: () => 1, - getRetryDelay: () => 1, - getRetryCount: () => 1, - }; - const mockRetryStrategy = { - mode: "mock", - acquireInitialRetryToken: jest.fn().mockResolvedValue(mockRetryToken), - refreshRetryTokenForRetry: jest.fn().mockResolvedValue(mockRetryToken), - recordSuccess: jest.fn(), - }; - const mockResponse = "mockResponse"; - const mockSuccess = { - response: mockResponse, - output: { - $metadata: {}, - }, - }; - const getErrorWithValues = (retryAfter: number | string, retryAfterHeaderName?: string) => { - const error = new Error("mockError"); - Object.defineProperty(error, "$response", { - value: { - headers: { [retryAfterHeaderName ? retryAfterHeaderName : "retry-after"]: String(retryAfter) }, - }, - }); - return error; - }; - - it("calls acquireInitialRetryToken and records success when next succeeds", async () => { - const next = jest.fn().mockResolvedValueOnce(mockSuccess); - const { response, output } = await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledWith(partitionId); - expect(mockRetryStrategy.recordSuccess).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.recordSuccess).toHaveBeenCalledWith(mockRetryToken); - expect(output.$metadata.attempts).toBe(1); - }); - - describe("throws when token cannot be refreshed", () => { - it("throw last request error", async () => { - const requestError = new Error("mockRequestError"); - (isThrottlingError as jest.Mock).mockReturnValue(true); - const next = jest.fn().mockRejectedValue(requestError); - const errorInfo = { - errorType: "THROTTLING", - }; - const mockRetryStrategy = { - mode: "mock", - acquireInitialRetryToken: jest.fn().mockResolvedValue(mockRetryToken), - refreshRetryTokenForRetry: jest.fn().mockRejectedValue(new Error("Cannot refresh token")), - recordSuccess: jest.fn(), - }; - try { - await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - } catch (error) { - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledWith(partitionId); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledWith(mockRetryToken, errorInfo); - expect(error).toStrictEqual(requestError); - expect(error.$metadata.attempts).toBe(1); - expect(error.$metadata.totalRetryDelay).toBeDefined(); - } - }); - }); - - describe("calls acquireInitialRetryToken and refreshes retry token", () => { - const mockError = new Error("mockError"); - it("sets throttling error type", async () => { - (isThrottlingError as jest.Mock).mockReturnValue(true); - const next = jest.fn().mockRejectedValueOnce(mockError).mockResolvedValueOnce(mockSuccess); - const errorInfo = { - errorType: "THROTTLING", - }; - const { response, output } = await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledWith(partitionId); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledWith(mockRetryToken, errorInfo); - expect(output.$metadata.attempts).toBe(2); - expect(output.$metadata.totalRetryDelay).toBeDefined(); - }); - it("sets transient error type", async () => { - (isTransientError as jest.Mock).mockReturnValue(true); - (isThrottlingError as jest.Mock).mockReturnValue(false); - const next = jest.fn().mockRejectedValueOnce(mockError).mockResolvedValueOnce(mockSuccess); - const errorInfo = { - errorType: "TRANSIENT", - }; - const { response, output } = await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledWith(partitionId); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledWith(mockRetryToken, errorInfo); - expect(output.$metadata.attempts).toBe(2); - expect(output.$metadata.totalRetryDelay).toBeDefined(); - }); - it("sets server error type", async () => { - (isServerError as jest.Mock).mockReturnValue(true); - (isTransientError as jest.Mock).mockReturnValue(false); - (isThrottlingError as jest.Mock).mockReturnValue(false); - const next = jest.fn().mockRejectedValueOnce(mockError).mockResolvedValueOnce(mockSuccess); - const errorInfo = { - errorType: "SERVER_ERROR", - }; - const { response, output } = await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledWith(partitionId); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledWith(mockRetryToken, errorInfo); - expect(output.$metadata.attempts).toBe(2); - expect(output.$metadata.totalRetryDelay).toBeDefined(); - }); - it("sets client error type", async () => { - (isServerError as jest.Mock).mockReturnValue(false); - (isTransientError as jest.Mock).mockReturnValue(false); - (isThrottlingError as jest.Mock).mockReturnValue(false); - const next = jest.fn().mockRejectedValueOnce(mockError).mockResolvedValueOnce(mockSuccess); - const errorInfo = { - errorType: "CLIENT_ERROR", - }; - const { response, output } = await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledWith(partitionId); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledWith(mockRetryToken, errorInfo); - expect(output.$metadata.attempts).toBe(2); - expect(output.$metadata.totalRetryDelay).toBeDefined(); - }); - - describe("when retry-after is not set", () => { - it("should not set retryAfter in errorInfo", async () => { - const { isInstance } = HttpResponse; - (isInstance as unknown as jest.Mock).mockReturnValue(true); - Object.defineProperty(mockError, "$response", { - value: { - headers: { ["other-header"]: "foo" }, - }, - }); - const next = jest.fn().mockRejectedValueOnce(mockError).mockResolvedValueOnce(mockSuccess); - const errorInfo = { - errorType: "CLIENT_ERROR", - }; - const { response, output } = await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledWith(partitionId); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledWith(mockRetryToken, errorInfo); - expect(output.$metadata.attempts).toBe(2); - expect(output.$metadata.totalRetryDelay).toBeDefined(); - }); - }); - - describe("when retry-after is set", () => { - const now = Date.now(); - const retryAfterDate = new Date(now + 3000); - const { isInstance } = HttpResponse; - (isInstance as unknown as jest.Mock).mockReturnValue(true); - const errorInfo = { - errorType: "CLIENT_ERROR", - retryAfterHint: retryAfterDate, - }; - it("parses retry-after from date string", async () => { - const error = getErrorWithValues(retryAfterDate.toISOString()); - const next = jest.fn().mockRejectedValueOnce(error).mockResolvedValueOnce(mockSuccess); - const { response, output } = await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledWith(partitionId); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledWith(mockRetryToken, errorInfo); - expect(output.$metadata.attempts).toBe(2); - expect(output.$metadata.totalRetryDelay).toBeDefined(); - }); - it("parses retry-after from seconds", async () => { - const error = getErrorWithValues(retryAfterDate.getTime() / 1000); - const next = jest.fn().mockRejectedValueOnce(error).mockResolvedValueOnce(mockSuccess); - const { response, output } = await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledWith(partitionId); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledWith(mockRetryToken, errorInfo); - expect(output.$metadata.attempts).toBe(2); - expect(output.$metadata.totalRetryDelay).toBeDefined(); - }); - it("parses retry-after from Retry-After header name", async () => { - const error = getErrorWithValues(retryAfterDate.toISOString(), "Retry-After"); - const next = jest.fn().mockRejectedValueOnce(error).mockResolvedValueOnce(mockSuccess); - const { response, output } = await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.acquireInitialRetryToken).toHaveBeenCalledWith(partitionId); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledTimes(1); - expect(mockRetryStrategy.refreshRetryTokenForRetry).toHaveBeenCalledWith(mockRetryToken, errorInfo); - expect(output.$metadata.attempts).toBe(2); - expect(output.$metadata.totalRetryDelay).toBeDefined(); - }); - (isInstance as unknown as jest.Mock).mockReturnValue(false); - }); - }); - - describe("retry headers", () => { - describe("not added if HttpRequest.isInstance returns false", () => { - it(`retry informational header: ${INVOCATION_ID_HEADER}`, async () => { - const next = jest.fn().mockResolvedValueOnce(mockSuccess); - await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(next).toHaveBeenCalledTimes(1); - expect(next.mock.calls[0][0].request.headers[INVOCATION_ID_HEADER]).not.toBeDefined(); - }); - }); - it(`header for each attempt as ${REQUEST_HEADER}`, async () => { - const next = jest.fn().mockResolvedValueOnce(mockSuccess); - await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(next).toHaveBeenCalledTimes(1); - expect(next.mock.calls[0][0].request.headers[REQUEST_HEADER]).not.toBeDefined(); - }); - - describe("added if HttpRequest.isInstance returns true", () => { - it(`retry informational header: ${INVOCATION_ID_HEADER}`, async () => { - const retryAfterDate = new Date(Date.now() + 3000); - const error = getErrorWithValues(retryAfterDate.toISOString()); - const { isInstance } = HttpRequest; - (isInstance as unknown as jest.Mock).mockReturnValue(true); - (isThrottlingError as jest.Mock).mockReturnValue(true); - const next = jest.fn().mockRejectedValueOnce(error).mockResolvedValueOnce(mockSuccess); - await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(next).toHaveBeenCalledTimes(2); - expect(next.mock.calls[0][0].request.headers[INVOCATION_ID_HEADER]).toBeDefined(); - expect(next.mock.calls[1][0].request.headers[INVOCATION_ID_HEADER]).toBeDefined(); - }); - it(`header for each attempt as ${REQUEST_HEADER}`, async () => { - const retryAfterDate = new Date(Date.now() + 3000); - const error = getErrorWithValues(retryAfterDate.toISOString()); - const { isInstance } = HttpRequest; - (isInstance as unknown as jest.Mock).mockReturnValue(true); - (isThrottlingError as jest.Mock).mockReturnValue(true); - const next = jest.fn().mockRejectedValueOnce(error).mockResolvedValueOnce(mockSuccess); - await retryMiddleware({ - maxAttempts: () => Promise.resolve(maxAttempts), - retryStrategy: jest.fn().mockResolvedValue({ ...mockRetryStrategy, maxAttempts }), - })( - next, - context - )(args as FinalizeHandlerArguments); - expect(next).toHaveBeenCalledTimes(2); - expect(next.mock.calls[0][0].request.headers[REQUEST_HEADER]).toBeDefined(); - expect(next.mock.calls[1][0].request.headers[REQUEST_HEADER]).toBeDefined(); - }); - }); - }); - }); -}); diff --git a/packages/middleware-retry/src/retryMiddleware.ts b/packages/middleware-retry/src/retryMiddleware.ts deleted file mode 100644 index e548e3d6f5fd..000000000000 --- a/packages/middleware-retry/src/retryMiddleware.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { HttpRequest, HttpResponse } from "@aws-sdk/protocol-http"; -import { isServerError, isThrottlingError, isTransientError } from "@aws-sdk/service-error-classification"; -import { - AbsoluteLocation, - FinalizeHandler, - FinalizeHandlerArguments, - FinalizeHandlerOutput, - FinalizeRequestHandlerOptions, - HandlerExecutionContext, - MetadataBearer, - Pluggable, - RetryErrorInfo, - RetryErrorType, - RetryStrategy, - RetryStrategyV2, - RetryToken, - SdkError, -} from "@aws-sdk/types"; -import { INVOCATION_ID_HEADER, REQUEST_HEADER } from "@aws-sdk/util-retry"; -import { v4 } from "uuid"; - -import { RetryResolvedConfig } from "./configurations"; -import { asSdkError } from "./util"; - -export const retryMiddleware = - (options: RetryResolvedConfig) => - ( - next: FinalizeHandler, - context: HandlerExecutionContext - ): FinalizeHandler => - async (args: FinalizeHandlerArguments): Promise> => { - let retryStrategy = await options.retryStrategy(); - const maxAttempts = await options.maxAttempts(); - - if (isRetryStrategyV2(retryStrategy)) { - retryStrategy = retryStrategy as RetryStrategyV2; - let retryToken: RetryToken = await retryStrategy.acquireInitialRetryToken(context["partition_id"]); - let lastError: SdkError = new Error(); - let attempts = 0; - let totalRetryDelay = 0; - const { request } = args; - if (HttpRequest.isInstance(request)) { - request.headers[INVOCATION_ID_HEADER] = v4(); - } - while (true) { - try { - if (HttpRequest.isInstance(request)) { - request.headers[REQUEST_HEADER] = `attempt=${attempts + 1}; max=${maxAttempts}`; - } - const { response, output } = await next(args); - retryStrategy.recordSuccess(retryToken); - output.$metadata.attempts = attempts + 1; - output.$metadata.totalRetryDelay = totalRetryDelay; - return { response, output }; - } catch (e) { - const retryErrorInfo = getRetryErrorInfo(e); - lastError = asSdkError(e); - try { - retryToken = await retryStrategy.refreshRetryTokenForRetry(retryToken, retryErrorInfo); - } catch (refreshError) { - if (!lastError.$metadata) { - lastError.$metadata = {}; - } - lastError.$metadata.attempts = attempts + 1; - lastError.$metadata.totalRetryDelay = totalRetryDelay; - throw lastError; - } - attempts = retryToken.getRetryCount(); - const delay = retryToken.getRetryDelay(); - totalRetryDelay += delay; - await new Promise((resolve) => setTimeout(resolve, delay)); - } - } - } else { - retryStrategy = retryStrategy as RetryStrategy; - if (retryStrategy?.mode) - context.userAgent = [...(context.userAgent || []), ["cfg/retry-mode", retryStrategy.mode]]; - - return retryStrategy.retry(next, args); - } - }; - -const isRetryStrategyV2 = (retryStrategy: RetryStrategy | RetryStrategyV2) => - typeof (retryStrategy as RetryStrategyV2).acquireInitialRetryToken !== "undefined" && - typeof (retryStrategy as RetryStrategyV2).refreshRetryTokenForRetry !== "undefined" && - typeof (retryStrategy as RetryStrategyV2).recordSuccess !== "undefined"; - -const getRetryErrorInfo = (error: SdkError): RetryErrorInfo => { - const errorInfo: RetryErrorInfo = { - errorType: getRetryErrorType(error), - }; - const retryAfterHint = getRetryAfterHint(error.$response); - if (retryAfterHint) { - errorInfo.retryAfterHint = retryAfterHint; - } - return errorInfo; -}; - -const getRetryErrorType = (error: SdkError): RetryErrorType => { - if (isThrottlingError(error)) return "THROTTLING"; - if (isTransientError(error)) return "TRANSIENT"; - if (isServerError(error)) return "SERVER_ERROR"; - return "CLIENT_ERROR"; -}; - -export const retryMiddlewareOptions: FinalizeRequestHandlerOptions & AbsoluteLocation = { - name: "retryMiddleware", - tags: ["RETRY"], - step: "finalizeRequest", - priority: "high", - override: true, -}; - -export const getRetryPlugin = (options: RetryResolvedConfig): Pluggable => ({ - applyToStack: (clientStack) => { - clientStack.add(retryMiddleware(options), retryMiddlewareOptions); - }, -}); - -export const getRetryAfterHint = (response: unknown): Date | undefined => { - if (!HttpResponse.isInstance(response)) return; - - const retryAfterHeaderName = Object.keys(response.headers).find((key) => key.toLowerCase() === "retry-after"); - if (!retryAfterHeaderName) return; - const retryAfter = response.headers[retryAfterHeaderName]; - - const retryAfterSeconds = Number(retryAfter); - if (!Number.isNaN(retryAfterSeconds)) return new Date(retryAfterSeconds * 1000); - - const retryAfterDate = new Date(retryAfter); - return retryAfterDate; -}; diff --git a/packages/middleware-retry/src/types.ts b/packages/middleware-retry/src/types.ts deleted file mode 100644 index 445d64db18c8..000000000000 --- a/packages/middleware-retry/src/types.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { SdkError } from "@aws-sdk/types"; - -/** - * Determines whether an error is retryable based on the number of retries - * already attempted, the HTTP status code, and the error received (if any). - * - * @param error - The error encountered. - */ -export interface RetryDecider { - (error: SdkError): boolean; -} - -/** - * Determines the number of milliseconds to wait before retrying an action. - * - * @param delayBase - The base delay (in milliseconds). - * @param attempts - The number of times the action has already been tried. - */ -export interface DelayDecider { - (delayBase: number, attempts: number): number; -} - -/** - * Interface that specifies the retry quota behavior. - */ -export interface RetryQuota { - /** - * returns true if retry tokens are available from the retry quota bucket. - */ - hasRetryTokens: (error: SdkError) => boolean; - - /** - * returns token amount from the retry quota bucket. - * throws error is retry tokens are not available. - */ - retrieveRetryTokens: (error: SdkError) => number; - - /** - * releases tokens back to the retry quota. - */ - releaseRetryTokens: (releaseCapacityAmount?: number) => void; -} - -export interface RateLimiter { - /** - * If there is sufficient capacity (tokens) available, it immediately returns. - * If there is not sufficient capacity, it will either sleep a certain amount - * of time until the rate limiter can retrieve a token from its token bucket - * or raise an exception indicating there is insufficient capacity. - */ - getSendToken: () => Promise; - - /** - * Updates the client sending rate based on response. - * If the response was successful, the capacity and fill rate are increased. - * If the response was a throttling response, the capacity and fill rate are - * decreased. Transient errors do not affect the rate limiter. - */ - updateClientSendingRate: (response: any) => void; -} diff --git a/packages/middleware-retry/src/util.ts b/packages/middleware-retry/src/util.ts deleted file mode 100644 index b5ef54644890..000000000000 --- a/packages/middleware-retry/src/util.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SdkError } from "@aws-sdk/types"; - -export const asSdkError = (error: unknown): SdkError => { - if (error instanceof Error) return error; - if (error instanceof Object) return Object.assign(new Error(), error); - if (typeof error === "string") return new Error(error); - return new Error(`AWS SDK error wrapper for ${error}`); -}; diff --git a/packages/middleware-serde/jest.config.js b/packages/middleware-serde/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/middleware-serde/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/middleware-serde/package.json b/packages/middleware-serde/package.json index e92661df3d42..3b8ef3e4daa5 100644 --- a/packages/middleware-serde/package.json +++ b/packages/middleware-serde/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,7 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "*", + "@smithy/middleware-serde": "^1.0.1", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/middleware-serde/src/deserializerMiddleware.spec.ts b/packages/middleware-serde/src/deserializerMiddleware.spec.ts deleted file mode 100644 index 8acad3cc94e1..000000000000 --- a/packages/middleware-serde/src/deserializerMiddleware.spec.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { deserializerMiddleware } from "./deserializerMiddleware"; - -describe("deserializerMiddleware", () => { - const mockNext = jest.fn(); - const mockDeserializer = jest.fn(); - - const mockOptions = { - endpoint: () => - Promise.resolve({ - protocol: "protocol", - hostname: "hostname", - path: "path", - }), - }; - - const mockArgs = { - input: { - inputKey: "inputValue", - }, - request: { - method: "GET", - headers: {}, - }, - }; - - const mockOutput = { - $metadata: { - statusCode: 200, - requestId: "requestId", - }, - outputKey: "outputValue", - }; - - const mockNextResponse = { - response: { - statusCode: 200, - headers: {}, - }, - }; - - const mockResponse = { - response: mockNextResponse.response, - output: mockOutput, - }; - - beforeEach(() => { - mockNext.mockResolvedValueOnce(mockNextResponse); - mockDeserializer.mockResolvedValueOnce(mockOutput); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("calls deserializer and populates response object", async () => { - await expect(deserializerMiddleware(mockOptions, mockDeserializer)(mockNext, {})(mockArgs)).resolves.toStrictEqual( - mockResponse - ); - - expect(mockNext).toHaveBeenCalledTimes(1); - expect(mockNext).toHaveBeenCalledWith(mockArgs); - expect(mockDeserializer).toHaveBeenCalledTimes(1); - expect(mockDeserializer).toHaveBeenCalledWith(mockNextResponse.response, mockOptions); - }); - - it("injects non-enumerable $response reference to deserializing exceptions", async () => { - const exception = Object.assign(new Error("MockException"), mockNextResponse.response); - mockDeserializer.mockReset(); - mockDeserializer.mockRejectedValueOnce(exception); - try { - await deserializerMiddleware(mockOptions, mockDeserializer)(mockNext, {})(mockArgs); - fail("DeserializerMiddleware should throw"); - } catch (e) { - expect(e).toMatchObject(exception); - expect(e.$response).toEqual(mockNextResponse.response); - expect(Object.keys(e)).not.toContain("$response"); - } - }); - - it("adds a hint about $response to the message of the thrown error", async () => { - const exception = Object.assign(new Error("MockException"), mockNextResponse.response); - mockDeserializer.mockReset(); - mockDeserializer.mockRejectedValueOnce(exception); - try { - await deserializerMiddleware(mockOptions, mockDeserializer)(mockNext, {})(mockArgs); - fail("DeserializerMiddleware should throw"); - } catch (e) { - expect(e.message).toContain( - "to see the raw response, inspect the hidden field {error}.$response on this object." - ); - } - }); -}); diff --git a/packages/middleware-serde/src/deserializerMiddleware.ts b/packages/middleware-serde/src/deserializerMiddleware.ts deleted file mode 100644 index eb847af71a96..000000000000 --- a/packages/middleware-serde/src/deserializerMiddleware.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - DeserializeHandler, - DeserializeHandlerArguments, - DeserializeHandlerOutput, - DeserializeMiddleware, - HandlerExecutionContext, - ResponseDeserializer, -} from "@aws-sdk/types"; - -export const deserializerMiddleware = - ( - options: RuntimeUtils, - deserializer: ResponseDeserializer - ): DeserializeMiddleware => - (next: DeserializeHandler, context: HandlerExecutionContext): DeserializeHandler => - async (args: DeserializeHandlerArguments): Promise> => { - const { response } = await next(args); - try { - const parsed = await deserializer(response, options); - return { - response, - output: parsed as Output, - }; - } catch (error) { - // For security reasons, the error response is not completely visible by default. - Object.defineProperty(error, "$response", { - value: response, - }); - - if (!('$metadata' in error)) { - // only apply this to non-ServiceException. - const hint = `Deserialization error: to see the raw response, inspect the hidden field {error}.$response on this object.`; - error.message += "\n " + hint; - } - - throw error; - } - }; diff --git a/packages/middleware-serde/src/index.ts b/packages/middleware-serde/src/index.ts index 166a2be21fdd..8fc7e38748d0 100644 --- a/packages/middleware-serde/src/index.ts +++ b/packages/middleware-serde/src/index.ts @@ -1,3 +1 @@ -export * from "./deserializerMiddleware"; -export * from "./serdePlugin"; -export * from "./serializerMiddleware"; +export * from "@smithy/middleware-serde"; diff --git a/packages/middleware-serde/src/serdePlugin.ts b/packages/middleware-serde/src/serdePlugin.ts deleted file mode 100644 index b43cd8e45df4..000000000000 --- a/packages/middleware-serde/src/serdePlugin.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - DeserializeHandlerOptions, - Endpoint, - EndpointBearer, - MetadataBearer, - MiddlewareStack, - Pluggable, - Provider, - RequestSerializer, - ResponseDeserializer, - SerializeHandlerOptions, - UrlParser, -} from "@aws-sdk/types"; - -import { deserializerMiddleware } from "./deserializerMiddleware"; -import { serializerMiddleware } from "./serializerMiddleware"; - -export const deserializerMiddlewareOption: DeserializeHandlerOptions = { - name: "deserializerMiddleware", - step: "deserialize", - tags: ["DESERIALIZER"], - override: true, -}; - -export const serializerMiddlewareOption: SerializeHandlerOptions = { - name: "serializerMiddleware", - step: "serialize", - tags: ["SERIALIZER"], - override: true, -}; - -// Type the modifies the EndpointBearer to make it compatible with Endpoints 2.0 change. -// Must be removed after all clients has been onboard the Endpoints 2.0 -export type V1OrV2Endpoint = { - // for v2 - urlParser?: UrlParser; - - // for v1 - endpoint?: Provider; -}; - -export function getSerdePlugin( - config: V1OrV2Endpoint, - serializer: RequestSerializer, - deserializer: ResponseDeserializer -): Pluggable { - return { - applyToStack: (commandStack: MiddlewareStack) => { - commandStack.add(deserializerMiddleware(config as SerDeContext, deserializer), deserializerMiddlewareOption); - commandStack.add(serializerMiddleware(config, serializer), serializerMiddlewareOption); - }, - }; -} diff --git a/packages/middleware-serde/src/serializerMiddleware.spec.ts b/packages/middleware-serde/src/serializerMiddleware.spec.ts deleted file mode 100644 index 079c0a15b8be..000000000000 --- a/packages/middleware-serde/src/serializerMiddleware.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { EndpointBearer } from "@aws-sdk/types"; - -import { serializerMiddleware } from "./serializerMiddleware"; - -describe("serializerMiddleware", () => { - const mockNext = jest.fn(); - const mockSerializer = jest.fn(); - - const mockOptions = { - endpoint: () => - Promise.resolve({ - protocol: "protocol", - hostname: "hostname", - path: "path", - }), - }; - - const mockRequest = { - method: "GET", - headers: {}, - }; - - const mockResponse = { - statusCode: 200, - headers: {}, - }; - - const mockOutput = { - $metadata: { - statusCode: 200, - requestId: "requestId", - }, - outputKey: "outputValue", - }; - - const mockReturn = { - response: mockResponse, - output: mockOutput, - }; - - const mockArgs = { - input: { - inputKey: "inputValue", - }, - }; - - beforeEach(() => { - mockNext.mockResolvedValueOnce(mockReturn); - mockSerializer.mockResolvedValueOnce(mockRequest); - }); - - it("calls serializer and populates request object", async () => { - await expect(serializerMiddleware(mockOptions, mockSerializer)(mockNext, {})(mockArgs)).resolves.toStrictEqual( - mockReturn - ); - - expect(mockSerializer).toHaveBeenCalledTimes(1); - expect(mockSerializer).toHaveBeenCalledWith(mockArgs.input, mockOptions); - expect(mockNext).toHaveBeenCalledTimes(1); - expect(mockNext).toHaveBeenCalledWith({ ...mockArgs, request: mockRequest }); - }); -}); diff --git a/packages/middleware-serde/src/serializerMiddleware.ts b/packages/middleware-serde/src/serializerMiddleware.ts deleted file mode 100644 index e42726aa5f3b..000000000000 --- a/packages/middleware-serde/src/serializerMiddleware.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - EndpointBearer, - HandlerExecutionContext, - RequestSerializer, - SerializeHandler, - SerializeHandlerArguments, - SerializeHandlerOutput, - SerializeMiddleware, -} from "@aws-sdk/types"; - -import type { V1OrV2Endpoint } from "./serdePlugin"; - -export const serializerMiddleware = - ( - options: V1OrV2Endpoint, - serializer: RequestSerializer - ): SerializeMiddleware => - (next: SerializeHandler, context: HandlerExecutionContext): SerializeHandler => - async (args: SerializeHandlerArguments): Promise> => { - const endpoint = - context.endpointV2?.url && options.urlParser - ? async () => options.urlParser!(context.endpointV2!.url as URL) - : options.endpoint!; - - if (!endpoint) { - throw new Error("No valid endpoint provider available."); - } - - const request = await serializer(args.input, { ...options, endpoint } as RuntimeUtils); - - return next({ - ...args, - request, - }); - }; diff --git a/packages/middleware-stack/jest.config.integ.js b/packages/middleware-stack/jest.config.integ.js deleted file mode 100644 index d09aba7398c7..000000000000 --- a/packages/middleware-stack/jest.config.integ.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - preset: "ts-jest", - testMatch: ["**/*.integ.spec.ts"], -}; diff --git a/packages/middleware-stack/jest.config.js b/packages/middleware-stack/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/middleware-stack/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/middleware-stack/package.json b/packages/middleware-stack/package.json index 9906d83f76f8..07df461d2f49 100644 --- a/packages/middleware-stack/package.json +++ b/packages/middleware-stack/package.json @@ -11,8 +11,7 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "extract:docs": "api-extractor run --local", - "test": "jest", - "test:integration": "jest -c jest.config.integ.js --passWithNoTests" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", @@ -24,6 +23,7 @@ "module": "./dist-es/index.js", "types": "./dist-types/index.d.ts", "dependencies": { + "@smithy/middleware-stack": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/middleware-stack/src/MiddlewareStack.spec.ts b/packages/middleware-stack/src/MiddlewareStack.spec.ts deleted file mode 100644 index 0d4c46ba374f..000000000000 --- a/packages/middleware-stack/src/MiddlewareStack.spec.ts +++ /dev/null @@ -1,395 +0,0 @@ -import { - DeserializeHandlerArguments, - DeserializeMiddleware, - FinalizeHandler, - FinalizeHandlerArguments, - InitializeHandler, - Pluggable, -} from "@aws-sdk/types"; - -import { constructStack } from "./MiddlewareStack"; - -type input = Array; -type output = object; - -//return tagged union to make compiler happy -const getConcatMiddleware = - (message: string) => - (next: FinalizeHandler): InitializeHandler => - (args: any) => - next({ - ...args, - input: args.input.concat(message), - request: undefined as any, - }); - -describe("MiddlewareStack", () => { - describe("add", () => { - it("should sort middleware based on step, priority, and ording of adding", async () => { - const stack = constructStack(); - const bMW = getConcatMiddleware("B"); - stack.add(bMW, { name: "B" }); - stack.add(getConcatMiddleware("A"), { - priority: "high", - }); - stack.add(getConcatMiddleware("D"), { - step: "build", - name: "D", - }); - stack.add(getConcatMiddleware("C"), { - step: "build", - priority: "high", - }); - stack.add(getConcatMiddleware("E"), { - step: "finalizeRequest", - }); - stack.add(getConcatMiddleware("F"), { step: "finalizeRequest" }); - stack.add(getConcatMiddleware("G") as DeserializeMiddleware, { - priority: "low", - step: "deserialize", - }); - const inner = jest.fn(); - - const composed = stack.resolve(inner, {} as any); - await composed({ input: [] }); - - expect(inner.mock.calls.length).toBe(1); - expect(inner).toBeCalledWith({ - input: ["A", "B", "C", "D", "E", "F", "G"], - }); - }); - - it("should throw if duplicated name is found", () => { - const stack = constructStack(); - const aMW = getConcatMiddleware("A"); - stack.add(aMW, { name: "A" }); - expect(() => stack.add(aMW, { name: "A" })).toThrow("Duplicate middleware name 'A'"); - }); - - describe("config: override", () => { - it("should override the middleware with same name if override config is set", async () => { - const stack = constructStack(); - stack.add(getConcatMiddleware("A"), { name: "A" }); - stack.add(getConcatMiddleware("override"), { name: "A", override: true }); - const inner = jest.fn(); - const composed = stack.resolve(inner, {} as any); - await composed({ input: [] }); - expect(inner.mock.calls.length).toBe(1); - expect(inner).toBeCalledWith({ - input: ["override"], - }); - }); - - it("should throw if overriding middleware with same name different position", () => { - const stack = constructStack(); - stack.add(getConcatMiddleware("A"), { name: "A" }); - expect(() => - stack.add(getConcatMiddleware("override"), { name: "A", step: "serialize", override: true }) - ).toThrow( - '"A" middleware with normal priority in initialize step cannot be overridden by same-name middleware with normal priority in serialize step.' - ); - }); - }); - }); - - describe("addRelativeTo", () => { - it("should allow adding middleware relatively based relation and order of adding", async () => { - const stack = constructStack(); - stack.addRelativeTo(getConcatMiddleware("H"), { - name: "H", - relation: "after", - toMiddleware: "G", - }); - stack.add(getConcatMiddleware("A"), { - name: "A", - }); - stack.addRelativeTo(getConcatMiddleware("C"), { - name: "C", - relation: "after", - toMiddleware: "A", - }); - stack.addRelativeTo(getConcatMiddleware("B"), { - name: "B", - relation: "after", - toMiddleware: "A", - }); - stack.addRelativeTo(getConcatMiddleware("D"), { - name: "D", - relation: "after", - toMiddleware: "C", - }); - stack.add(getConcatMiddleware("G"), { - name: "G", - priority: "low", - }); - stack.addRelativeTo(getConcatMiddleware("E"), { - name: "E", - relation: "before", - toMiddleware: "F", - }); - stack.addRelativeTo(getConcatMiddleware("F"), { - name: "F", - relation: "before", - toMiddleware: "G", - }); - const inner = jest.fn(); - const composed = stack.resolve(inner, {} as any); - await composed({ input: [] }); - expect(inner.mock.calls.length).toBe(1); - expect(inner).toBeCalledWith({ input: ["A", "B", "C", "D", "E", "F", "G", "H"] }); - }); - - it("should add relative middleware within the scope of adjacent absolute middleware", async () => { - const stack = constructStack(); - stack.addRelativeTo(getConcatMiddleware("B"), { - name: "B", - relation: "after", - toMiddleware: "A", - }); - stack.add(getConcatMiddleware("A"), { name: "A" }); - stack.addRelativeTo(getConcatMiddleware("C"), { - name: "C", - relation: "before", - toMiddleware: "D", - }); - stack.add(getConcatMiddleware("D"), { name: "D" }); - const inner = jest.fn(); - const composed = stack.resolve(inner, {} as any); - await composed({ input: [] }); - expect(inner.mock.calls.length).toBe(1); - expect(inner).toBeCalledWith({ input: ["A", "B", "C", "D"] }); - }); - - it("should not add self-referenced relative middleware", async () => { - const stack = constructStack(); - stack.addRelativeTo(getConcatMiddleware("A"), { - name: "A", - relation: "before", - toMiddleware: "B", - }); - stack.addRelativeTo(getConcatMiddleware("B"), { - name: "B", - relation: "before", - toMiddleware: "C", - }); - stack.addRelativeTo(getConcatMiddleware("C"), { - name: "C", - relation: "after", - toMiddleware: "A", - }); - const inner = jest.fn(); - const composed = stack.resolve(inner, {} as any); - await composed({ input: [] }); - expect(inner.mock.calls.length).toBe(1); - expect(inner).toBeCalledWith({ input: [] }); - }); - - it("should throw if add middleware relative to non-exist middleware", async () => { - expect.assertions(1); - const stack = constructStack(); - stack.addRelativeTo(getConcatMiddleware("foo"), { - name: "foo", - relation: "before", - toMiddleware: "non_exist", - }); - const inner = jest.fn(); - try { - stack.resolve(inner, {} as any); - } catch (e) { - expect(e.message).toBe("non_exist is not found when adding foo middleware before non_exist"); - } - }); - - describe("config: override", () => { - it("should override the middleware with same name if override config is set", async () => { - const stack = constructStack(); - stack.add(getConcatMiddleware("A"), { name: "A" }); - stack.addRelativeTo(getConcatMiddleware("B"), { name: "B", relation: "after", toMiddleware: "A" }); - stack.addRelativeTo(getConcatMiddleware("override"), { - name: "B", - relation: "after", - toMiddleware: "A", - override: true, - }); - const inner = jest.fn(); - const composed = stack.resolve(inner, {} as any); - await composed({ input: [] }); - expect(inner.mock.calls.length).toBe(1); - expect(inner).toBeCalledWith({ - input: ["A", "override"], - }); - }); - - it("should throw if overriding middleware with same name different position", () => { - const stack = constructStack(); - stack.add(getConcatMiddleware("A"), { name: "A" }); - stack.addRelativeTo(getConcatMiddleware("B"), { name: "B", relation: "after", toMiddleware: "A" }); - expect(() => - stack.addRelativeTo(getConcatMiddleware("override"), { - name: "B", - relation: "before", - toMiddleware: "A", - override: true, - }) - ).toThrow( - '"B" middleware after "A" middleware cannot be overridden by same-name middleware before "A" middleware.' - ); - }); - }); - }); - - describe("clone", () => { - it("should allow cloning", async () => { - const stack = constructStack(); - const bMiddleware = getConcatMiddleware("B"); - stack.add(bMiddleware); - stack.add(getConcatMiddleware("A"), { - name: "A", - priority: "high", - }); - const secondStack = stack.clone(); - const inner = jest.fn(); - await secondStack.resolve(inner, {} as any)({ input: [] }); - expect(inner.mock.calls.length).toBe(1); - expect(inner).toBeCalledWith({ input: ["A", "B"] }); - // validate adding middleware to cloned stack won't affect the original stack. - inner.mockClear(); - secondStack.add(getConcatMiddleware("C")); - await secondStack.resolve(inner, {} as any)({ input: [] }); - expect(inner).toBeCalledWith({ input: ["A", "B", "C"] }); - inner.mockClear(); - await stack.resolve(inner, {} as any)({ input: [] }); - expect(inner).toBeCalledWith({ input: ["A", "B"] }); - }); - }); - - describe("concat", () => { - it("should allow combining stacks", async () => { - const stack = constructStack(); - stack.add(getConcatMiddleware("A")); - stack.add(getConcatMiddleware("B"), { - name: "B", - priority: "low", - }); - - const secondStack = constructStack(); - secondStack.add(getConcatMiddleware("D"), { - step: "build", - priority: "low", - }); - secondStack.addRelativeTo(getConcatMiddleware("C"), { - relation: "after", - toMiddleware: "B", - }); - - const inner = jest.fn(); - await stack.concat(secondStack).resolve(inner, {} as any)({ input: [] }); - expect(inner.mock.calls.length).toBe(1); - expect(inner).toBeCalledWith({ input: ["A", "B", "C", "D"] }); - }); - - it("should not touch the stack of the concat() caller or the parameter", async () => { - const stack = constructStack(); - stack.add(getConcatMiddleware("A")); - const secondStack = constructStack(); - secondStack.add(getConcatMiddleware("B")); - const inner = jest.fn(); - await stack.concat(secondStack).resolve(inner, {} as any)({ input: [] }); - expect(inner.mock.calls.length).toBe(1); - expect(inner).toBeCalledWith({ input: ["A", "B"] }); - inner.mockClear(); - await secondStack.resolve(inner, {} as any)({ input: [] }); - expect(inner).toBeCalledWith({ input: ["B"] }); - inner.mockClear(); - await stack.resolve(inner, {} as any)({ input: [] }); - expect(inner).toBeCalledWith({ input: ["A"] }); - }); - }); - - describe("remove", () => { - it("should remove middleware by name", async () => { - const stack = constructStack(); - stack.add(getConcatMiddleware("don't remove me"), { name: "notRemove" }); - stack.addRelativeTo(getConcatMiddleware("remove me!"), { - relation: "after", - toMiddleware: "notRemove", - name: "toRemove", - }); - - await stack.resolve(({ input }: FinalizeHandlerArguments>) => { - expect(input.sort()).toEqual(["don't remove me", "remove me!"]); - return Promise.resolve({ response: {} }); - }, {} as any)({ input: [] }); - - stack.remove("toRemove"); - - const inner = jest.fn(); - await stack.resolve(inner, {} as any)({ input: [] }); - expect(inner.mock.calls.length).toBe(1); - expect(inner).toBeCalledWith({ input: ["don't remove me"] }); - }); - - it("should remove middleware by reference", async () => { - const stack = constructStack(); - const mw = getConcatMiddleware("remove all references of me"); - stack.add(mw, { name: "toRemove1" }); - stack.add(getConcatMiddleware("don't remove me!")); - stack.add(mw, { name: "toRemove2" }); - stack.remove(mw); - - const inner = jest.fn(); - await stack.resolve(inner, {} as any)({ input: [] }); - expect(inner.mock.calls.length).toBe(1); - expect(inner).toBeCalledWith({ input: ["don't remove me!"] }); - }); - }); - - describe("removeByTag", () => { - it("should allow the removal of middleware by tag", async () => { - const stack = constructStack(); - stack.add(getConcatMiddleware("not removed"), { - name: "not removed", - tags: ["foo", "bar"], - }); - stack.addRelativeTo(getConcatMiddleware("remove me!"), { - relation: "after", - toMiddleware: "not removed", - tags: ["foo", "bar", "baz"], - }); - - await stack.resolve(({ input }: FinalizeHandlerArguments>) => { - expect(input.sort()).toEqual(["not removed", "remove me!"]); - return Promise.resolve({ response: {} }); - }, {} as any)({ input: [] }); - - stack.removeByTag("baz"); - - await stack.resolve(({ input }: DeserializeHandlerArguments>) => { - expect(input).toEqual(["not removed"]); - return Promise.resolve({ response: {} }); - }, {} as any)({ input: [] }); - }); - }); - - describe("use", () => { - it("should apply customizations from pluggables", async () => { - const stack = constructStack(); - const plugin: Pluggable = { - applyToStack: (stack) => { - stack.addRelativeTo(getConcatMiddleware("second"), { - relation: "after", - toMiddleware: "first", - }); - stack.add(getConcatMiddleware("first"), { name: "first" }); - }, - }; - stack.use(plugin); - const inner = jest.fn(({ input }: DeserializeHandlerArguments) => { - expect(input).toEqual(["first", "second"]); - return Promise.resolve({ response: {} }); - }); - await stack.resolve(inner, {} as any)({ input: [] }); - expect(inner.mock.calls.length).toBe(1); - }); - }); -}); diff --git a/packages/middleware-stack/src/MiddlewareStack.ts b/packages/middleware-stack/src/MiddlewareStack.ts deleted file mode 100644 index 2be61a15d056..000000000000 --- a/packages/middleware-stack/src/MiddlewareStack.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { - AbsoluteLocation, - DeserializeHandler, - Handler, - HandlerExecutionContext, - HandlerOptions, - MiddlewareStack, - MiddlewareType, - Pluggable, - Priority, - RelativeLocation, - Step, -} from "@aws-sdk/types"; - -import { AbsoluteMiddlewareEntry, MiddlewareEntry, Normalized, RelativeMiddlewareEntry } from "./types"; - -export const constructStack = (): MiddlewareStack => { - let absoluteEntries: AbsoluteMiddlewareEntry[] = []; - let relativeEntries: RelativeMiddlewareEntry[] = []; - const entriesNameSet: Set = new Set(); - - const sort = >(entries: T[]): T[] => - entries.sort( - (a, b) => - stepWeights[b.step] - stepWeights[a.step] || - priorityWeights[b.priority || "normal"] - priorityWeights[a.priority || "normal"] - ); - - const removeByName = (toRemove: string): boolean => { - let isRemoved = false; - const filterCb = (entry: MiddlewareEntry): boolean => { - if (entry.name && entry.name === toRemove) { - isRemoved = true; - entriesNameSet.delete(toRemove); - return false; - } - return true; - }; - absoluteEntries = absoluteEntries.filter(filterCb); - relativeEntries = relativeEntries.filter(filterCb); - return isRemoved; - }; - - const removeByReference = (toRemove: MiddlewareType): boolean => { - let isRemoved = false; - const filterCb = (entry: MiddlewareEntry): boolean => { - if (entry.middleware === toRemove) { - isRemoved = true; - if (entry.name) entriesNameSet.delete(entry.name); - return false; - } - return true; - }; - absoluteEntries = absoluteEntries.filter(filterCb); - relativeEntries = relativeEntries.filter(filterCb); - return isRemoved; - }; - - const cloneTo = ( - toStack: MiddlewareStack - ): MiddlewareStack => { - absoluteEntries.forEach((entry) => { - //@ts-ignore - toStack.add(entry.middleware, { ...entry }); - }); - relativeEntries.forEach((entry) => { - //@ts-ignore - toStack.addRelativeTo(entry.middleware, { ...entry }); - }); - return toStack; - }; - - const expandRelativeMiddlewareList = ( - from: Normalized, Input, Output> - ): MiddlewareEntry[] => { - const expandedMiddlewareList: MiddlewareEntry[] = []; - from.before.forEach((entry) => { - if (entry.before.length === 0 && entry.after.length === 0) { - expandedMiddlewareList.push(entry); - } else { - expandedMiddlewareList.push(...expandRelativeMiddlewareList(entry)); - } - }); - expandedMiddlewareList.push(from); - from.after.reverse().forEach((entry) => { - if (entry.before.length === 0 && entry.after.length === 0) { - expandedMiddlewareList.push(entry); - } else { - expandedMiddlewareList.push(...expandRelativeMiddlewareList(entry)); - } - }); - return expandedMiddlewareList; - }; - - /** - * Get a final list of middleware in the order of being executed in the resolved handler. - * @param debug - don't throw, getting info only. - */ - const getMiddlewareList = (debug = false): Array> => { - const normalizedAbsoluteEntries: Normalized, Input, Output>[] = []; - const normalizedRelativeEntries: Normalized, Input, Output>[] = []; - const normalizedEntriesNameMap: Record, Input, Output>> = {}; - - absoluteEntries.forEach((entry) => { - const normalizedEntry = { - ...entry, - before: [], - after: [], - }; - if (normalizedEntry.name) normalizedEntriesNameMap[normalizedEntry.name] = normalizedEntry; - normalizedAbsoluteEntries.push(normalizedEntry); - }); - - relativeEntries.forEach((entry) => { - const normalizedEntry = { - ...entry, - before: [], - after: [], - }; - if (normalizedEntry.name) normalizedEntriesNameMap[normalizedEntry.name] = normalizedEntry; - normalizedRelativeEntries.push(normalizedEntry); - }); - - normalizedRelativeEntries.forEach((entry) => { - if (entry.toMiddleware) { - const toMiddleware = normalizedEntriesNameMap[entry.toMiddleware]; - if (toMiddleware === undefined) { - if (debug) { - return; - } - throw new Error( - `${entry.toMiddleware} is not found when adding ${entry.name || "anonymous"} middleware ${entry.relation} ${ - entry.toMiddleware - }` - ); - } - if (entry.relation === "after") { - toMiddleware.after.push(entry); - } - if (entry.relation === "before") { - toMiddleware.before.push(entry); - } - } - }); - - const mainChain = sort(normalizedAbsoluteEntries) - .map(expandRelativeMiddlewareList) - .reduce((wholeList, expendedMiddlewareList) => { - // TODO: Replace it with Array.flat(); - wholeList.push(...expendedMiddlewareList); - return wholeList; - }, [] as MiddlewareEntry[]); - return mainChain; - }; - - const stack: MiddlewareStack = { - add: (middleware: MiddlewareType, options: HandlerOptions & AbsoluteLocation = {}) => { - const { name, override } = options; - const entry: AbsoluteMiddlewareEntry = { - step: "initialize", - priority: "normal", - middleware, - ...options, - }; - if (name) { - if (entriesNameSet.has(name)) { - if (!override) throw new Error(`Duplicate middleware name '${name}'`); - const toOverrideIndex = absoluteEntries.findIndex((entry) => entry.name === name); - const toOverride = absoluteEntries[toOverrideIndex]; - if (toOverride.step !== entry.step || toOverride.priority !== entry.priority) { - throw new Error( - `"${name}" middleware with ${toOverride.priority} priority in ${toOverride.step} step cannot be ` + - `overridden by same-name middleware with ${entry.priority} priority in ${entry.step} step.` - ); - } - absoluteEntries.splice(toOverrideIndex, 1); - } - entriesNameSet.add(name); - } - absoluteEntries.push(entry); - }, - - addRelativeTo: (middleware: MiddlewareType, options: HandlerOptions & RelativeLocation) => { - const { name, override } = options; - const entry: RelativeMiddlewareEntry = { - middleware, - ...options, - }; - if (name) { - if (entriesNameSet.has(name)) { - if (!override) throw new Error(`Duplicate middleware name '${name}'`); - const toOverrideIndex = relativeEntries.findIndex((entry) => entry.name === name); - const toOverride = relativeEntries[toOverrideIndex]; - if (toOverride.toMiddleware !== entry.toMiddleware || toOverride.relation !== entry.relation) { - throw new Error( - `"${name}" middleware ${toOverride.relation} "${toOverride.toMiddleware}" middleware cannot be overridden ` + - `by same-name middleware ${entry.relation} "${entry.toMiddleware}" middleware.` - ); - } - relativeEntries.splice(toOverrideIndex, 1); - } - entriesNameSet.add(name); - } - relativeEntries.push(entry); - }, - - clone: () => cloneTo(constructStack()), - - use: (plugin: Pluggable) => { - plugin.applyToStack(stack); - }, - - remove: (toRemove: MiddlewareType | string): boolean => { - if (typeof toRemove === "string") return removeByName(toRemove); - else return removeByReference(toRemove); - }, - - removeByTag: (toRemove: string): boolean => { - let isRemoved = false; - const filterCb = (entry: MiddlewareEntry): boolean => { - const { tags, name } = entry; - if (tags && tags.includes(toRemove)) { - if (name) entriesNameSet.delete(name); - isRemoved = true; - return false; - } - return true; - }; - absoluteEntries = absoluteEntries.filter(filterCb); - relativeEntries = relativeEntries.filter(filterCb); - return isRemoved; - }, - - concat: ( - from: MiddlewareStack - ): MiddlewareStack => { - const cloned = cloneTo(constructStack()); - cloned.use(from); - return cloned; - }, - - applyToStack: cloneTo, - - identify: (): string[] => { - return getMiddlewareList(true).map((mw: MiddlewareEntry) => { - return mw.name + ": " + (mw.tags || []).join(","); - }); - }, - - resolve: ( - handler: DeserializeHandler, - context: HandlerExecutionContext - ): Handler => { - for (const middleware of getMiddlewareList() - .map((entry) => entry.middleware) - .reverse()) { - handler = middleware(handler as Handler, context) as any; - } - return handler as Handler; - }, - }; - return stack; -}; - -const stepWeights: { [key in Step]: number } = { - initialize: 5, - serialize: 4, - build: 3, - finalizeRequest: 2, - deserialize: 1, -}; - -const priorityWeights: { [key in Priority]: number } = { - high: 3, - normal: 2, - low: 1, -}; diff --git a/packages/middleware-stack/src/index.ts b/packages/middleware-stack/src/index.ts index 16f56ce96abb..05c5a669d24f 100644 --- a/packages/middleware-stack/src/index.ts +++ b/packages/middleware-stack/src/index.ts @@ -1 +1 @@ -export * from "./MiddlewareStack"; +export * from "@smithy/middleware-stack"; diff --git a/packages/middleware-stack/src/types.ts b/packages/middleware-stack/src/types.ts deleted file mode 100644 index d42708c67acd..000000000000 --- a/packages/middleware-stack/src/types.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { AbsoluteLocation, HandlerOptions, MiddlewareType, Priority, RelativeLocation, Step } from "@aws-sdk/types"; - -export interface MiddlewareEntry extends HandlerOptions { - middleware: MiddlewareType; -} - -export interface AbsoluteMiddlewareEntry - extends MiddlewareEntry, - AbsoluteLocation { - step: Step; - priority: Priority; -} - -export interface RelativeMiddlewareEntry - extends MiddlewareEntry, - RelativeLocation {} - -export type Normalized< - T extends MiddlewareEntry, - Input extends object = {}, - Output extends object = {} -> = T & { - after: Normalized, Input, Output>[]; - before: Normalized, Input, Output>[]; -}; - -export interface NormalizedRelativeEntry extends HandlerOptions { - step: Step; - middleware: MiddlewareType; - next?: NormalizedRelativeEntry; - prev?: NormalizedRelativeEntry; - priority: null; -} - -export type NamedMiddlewareEntriesMap = Record< - string, - MiddlewareEntry ->; diff --git a/packages/node-config-provider/jest.config.js b/packages/node-config-provider/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/node-config-provider/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/node-config-provider/package.json b/packages/node-config-provider/package.json index 48bee3979afb..6f5004ceb0e1 100644 --- a/packages/node-config-provider/package.json +++ b/packages/node-config-provider/package.json @@ -10,7 +10,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest --passWithNoTests" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", @@ -22,9 +22,7 @@ "module": "./dist-es/index.js", "types": "./dist-types/index.d.ts", "dependencies": { - "@aws-sdk/property-provider": "*", - "@aws-sdk/shared-ini-file-loader": "*", - "@aws-sdk/types": "*", + "@smithy/node-config-provider": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/node-config-provider/src/configLoader.spec.ts b/packages/node-config-provider/src/configLoader.spec.ts deleted file mode 100644 index 7c7f52a4c008..000000000000 --- a/packages/node-config-provider/src/configLoader.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { chain, fromStatic, memoize } from "@aws-sdk/property-provider"; -import { Profile } from "@aws-sdk/types"; - -import { loadConfig } from "./configLoader"; -import { fromEnv } from "./fromEnv"; -import { fromSharedConfigFiles, SharedConfigInit } from "./fromSharedConfigFiles"; - -jest.mock("./fromEnv"); -jest.mock("./fromSharedConfigFiles"); -jest.mock("@aws-sdk/property-provider"); - -describe("loadConfig", () => { - const configuration: SharedConfigInit = { - profile: "profile", - }; - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("passes fromEnv(), fromSharedConfigFiles() and fromStatic() to chain", () => { - const mockFromEnvReturn = "mockFromEnvReturn"; - (fromEnv as jest.Mock).mockReturnValueOnce(mockFromEnvReturn); - const mockFromSharedConfigFilesReturn = "mockFromSharedConfigFilesReturn"; - (fromSharedConfigFiles as jest.Mock).mockReturnValueOnce(mockFromSharedConfigFilesReturn); - const mockFromStatic = "mockFromStatic"; - (fromStatic as jest.Mock).mockReturnValueOnce(mockFromStatic); - // Using Record instead of NodeJS.ProcessEnv, in order to not get type errors in non node environments - const envVarSelector = (env: Record) => env["AWS_CONFIG_FOO"]; - const configKey = (profile: Profile) => profile["aws_config_foo"]; - const defaultValue = "foo-value"; - loadConfig( - { - environmentVariableSelector: envVarSelector, - configFileSelector: configKey, - default: defaultValue, - }, - configuration - ); - expect(fromEnv).toHaveBeenCalledTimes(1); - expect(fromEnv).toHaveBeenCalledWith(envVarSelector); - expect(fromSharedConfigFiles).toHaveBeenCalledTimes(1); - expect(fromSharedConfigFiles).toHaveBeenCalledWith(configKey, configuration); - expect(fromStatic).toHaveBeenCalledTimes(1); - expect(fromStatic).toHaveBeenCalledWith(defaultValue); - expect(chain).toHaveBeenCalledTimes(1); - expect(chain).toHaveBeenCalledWith(mockFromEnvReturn, mockFromSharedConfigFilesReturn, mockFromStatic); - }); - - it("passes output of chain to memoize", () => { - const mockChainReturn = "mockChainReturn"; - (chain as jest.Mock).mockReturnValueOnce(mockChainReturn); - loadConfig({} as any); - expect(chain).toHaveBeenCalledTimes(1); - expect(memoize).toHaveBeenCalledTimes(1); - expect(memoize).toHaveBeenCalledWith(mockChainReturn); - }); - - it("returns output memoize", () => { - const mockMemoizeReturn = "mockMemoizeReturn"; - (memoize as jest.Mock).mockReturnValueOnce(mockMemoizeReturn); - expect(loadConfig({} as any)).toEqual(mockMemoizeReturn); - }); -}); diff --git a/packages/node-config-provider/src/configLoader.ts b/packages/node-config-provider/src/configLoader.ts deleted file mode 100644 index c42c17bf6b29..000000000000 --- a/packages/node-config-provider/src/configLoader.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { chain, memoize } from "@aws-sdk/property-provider"; -import { Provider } from "@aws-sdk/types"; - -import { fromEnv, GetterFromEnv } from "./fromEnv"; -import { fromSharedConfigFiles, GetterFromConfig, SharedConfigInit } from "./fromSharedConfigFiles"; -import { fromStatic, FromStaticConfig } from "./fromStatic"; - -export type LocalConfigOptions = SharedConfigInit; - -export interface LoadedConfigSelectors { - /** - * A getter function getting the config values from all the environment - * variables. - */ - environmentVariableSelector: GetterFromEnv; - /** - * A getter function getting config values associated with the inferred - * profile from shared INI files - */ - configFileSelector: GetterFromConfig; - /** - * Default value or getter - */ - default: FromStaticConfig; -} - -export const loadConfig = ( - { environmentVariableSelector, configFileSelector, default: defaultValue }: LoadedConfigSelectors, - configuration: LocalConfigOptions = {} -): Provider => - memoize( - chain( - fromEnv(environmentVariableSelector), - fromSharedConfigFiles(configFileSelector, configuration), - fromStatic(defaultValue) - ) - ); diff --git a/packages/node-config-provider/src/fromEnv.spec.ts b/packages/node-config-provider/src/fromEnv.spec.ts deleted file mode 100644 index 5ad9cf061330..000000000000 --- a/packages/node-config-provider/src/fromEnv.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { CredentialsProviderError } from "@aws-sdk/property-provider"; - -import { fromEnv, GetterFromEnv } from "./fromEnv"; - -describe("fromEnv", () => { - describe("with env var getter", () => { - const envVarName = "ENV_VAR_NAME"; - - // Using Record instead of NodeJS.ProcessEnv, in order to not get type errors in non node environments - const envVarGetter: GetterFromEnv = (env: Record) => env[envVarName]!; - const envVarValue = process.env[envVarName]; - const mockEnvVarValue = "mockEnvVarValue"; - - const getCredentialsProviderError = (getter: GetterFromEnv) => - new CredentialsProviderError(`Cannot load config from environment variables with getter: ${getter}`); - - beforeEach(() => { - delete process.env[envVarName]; - }); - - afterAll(() => { - process.env[envVarName] = envVarValue; - }); - - it(`returns string value in '${envVarName}' env var when set`, () => { - process.env[envVarName] = mockEnvVarValue; - return expect(fromEnv(envVarGetter)()).resolves.toBe(mockEnvVarValue); - }); - - it("return complex value from the getter", () => { - type Value = { Foo: string }; - const value: Value = { Foo: "bar" }; - const getter: (env: any) => Value = jest.fn().mockReturnValue(value); - // Validate the generic type works - return expect(fromEnv(getter)()).resolves.toEqual(value); - }); - - it(`throws when '${envVarName}' env var is not set`, () => { - expect.assertions(1); - return expect(fromEnv(envVarGetter)()).rejects.toMatchObject(getCredentialsProviderError(envVarGetter)); - }); - - it("throws when the getter function throws", () => { - const exception = new Error("Exception when getting the config"); - const getter: (env: any) => any = jest.fn().mockRejectedValue(exception); - return expect(fromEnv(getter)()).rejects.toEqual(exception); - }); - }); -}); diff --git a/packages/node-config-provider/src/fromEnv.ts b/packages/node-config-provider/src/fromEnv.ts deleted file mode 100644 index e3eae8db2b8d..000000000000 --- a/packages/node-config-provider/src/fromEnv.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { CredentialsProviderError } from "@aws-sdk/property-provider"; -import { Provider } from "@aws-sdk/types"; - -// Using Record instead of NodeJS.ProcessEnv, in order to not get type errors in non node environments -export type GetterFromEnv = (env: Record) => T | undefined; - -/** - * Get config value given the environment variable name or getter from - * environment variable. - */ -export const fromEnv = - (envVarSelector: GetterFromEnv): Provider => - async () => { - try { - const config = envVarSelector(process.env); - if (config === undefined) { - throw new Error(); - } - return config as T; - } catch (e) { - throw new CredentialsProviderError( - e.message || `Cannot load config from environment variables with getter: ${envVarSelector}` - ); - } - }; diff --git a/packages/node-config-provider/src/fromSharedConfigFiles.spec.ts b/packages/node-config-provider/src/fromSharedConfigFiles.spec.ts deleted file mode 100644 index 659d96815075..000000000000 --- a/packages/node-config-provider/src/fromSharedConfigFiles.spec.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { CredentialsProviderError } from "@aws-sdk/property-provider"; -import { getProfileName, loadSharedConfigFiles } from "@aws-sdk/shared-ini-file-loader"; -import { ParsedIniData, Profile } from "@aws-sdk/types"; - -import { fromSharedConfigFiles, GetterFromConfig, SharedConfigInit } from "./fromSharedConfigFiles"; - -jest.mock("@aws-sdk/shared-ini-file-loader", () => ({ - getProfileName: jest.fn(), - loadSharedConfigFiles: jest.fn(), -})); - -describe("fromSharedConfigFiles", () => { - const configKey = "config_key"; - const configGetter: GetterFromConfig = (profile: Profile) => profile[configKey]; - - const getCredentialsProviderError = (profile: string, getter: GetterFromConfig) => - new CredentialsProviderError( - `Cannot load config for profile ${profile} in SDK configuration files with getter: ${getter}` - ); - - describe("loadedConfig", () => { - const mockConfigAnswer = "mockConfigAnswer"; - const mockConfigNotAnswer = "mockConfigNotAnswer"; - const mockCredentialsAnswer = "mockCredentialsAnswer"; - const mockCredentialsNotAnswer = "mockCredentialsNotAnswer"; - - type LoadedConfigTestData = { - message: string; - iniDataInConfig: ParsedIniData; - iniDataInCredentials: ParsedIniData; - } & SharedConfigInit; - - const loadedConfigResolves: (LoadedConfigTestData & { - configValueToVerify: string; - })[] = [ - { - message: "returns configValue from default profile", - iniDataInConfig: { - default: { [configKey]: mockConfigAnswer }, - }, - iniDataInCredentials: { - default: { [configKey]: mockCredentialsNotAnswer }, - }, - configValueToVerify: mockConfigAnswer, - }, - { - message: "returns configValue from designated profile", - iniDataInConfig: { - default: { [configKey]: mockConfigNotAnswer }, - foo: { [configKey]: mockConfigAnswer }, - }, - iniDataInCredentials: { - foo: { [configKey]: mockCredentialsNotAnswer }, - }, - profile: "foo", - configValueToVerify: mockConfigAnswer, - }, - { - message: "returns configValue from credentials file if preferred", - iniDataInConfig: { - default: { [configKey]: mockConfigNotAnswer }, - foo: { [configKey]: mockConfigNotAnswer }, - }, - iniDataInCredentials: { - foo: { [configKey]: mockCredentialsAnswer }, - }, - profile: "foo", - preferredFile: "credentials", - configValueToVerify: mockCredentialsAnswer, - }, - { - message: "returns configValue from config file if preferred credentials file doesn't contain config", - iniDataInConfig: { - foo: { [configKey]: mockConfigAnswer }, - }, - iniDataInCredentials: {}, - configValueToVerify: mockConfigAnswer, - preferredFile: "credentials", - profile: "foo", - }, - { - message: "returns configValue from credential file if preferred config file doesn't contain config", - iniDataInConfig: {}, - iniDataInCredentials: { - foo: { [configKey]: mockCredentialsAnswer }, - }, - configValueToVerify: mockCredentialsAnswer, - profile: "foo", - }, - ]; - - const loadedConfigRejects: LoadedConfigTestData[] = [ - { - message: "rejects if default profile is not present and profile value is not passed", - iniDataInConfig: { - foo: { [configKey]: mockConfigNotAnswer }, - }, - iniDataInCredentials: {}, - }, - { - message: "rejects if designated profile is not present", - iniDataInConfig: { - default: { [configKey]: mockConfigNotAnswer }, - }, - iniDataInCredentials: {}, - profile: "foo", - }, - ]; - - loadedConfigResolves.forEach( - ({ message, iniDataInConfig, iniDataInCredentials, configValueToVerify, profile, preferredFile }) => { - it(message, () => { - (loadSharedConfigFiles as jest.Mock).mockResolvedValueOnce({ - configFile: iniDataInConfig, - credentialsFile: iniDataInCredentials, - }); - (getProfileName as jest.Mock).mockReturnValueOnce(profile ?? "default"); - return expect(fromSharedConfigFiles(configGetter, { profile, preferredFile })()).resolves.toBe( - configValueToVerify - ); - }); - } - ); - - loadedConfigRejects.forEach(({ message, iniDataInConfig, iniDataInCredentials, profile, preferredFile }) => { - it(message, () => { - (loadSharedConfigFiles as jest.Mock).mockResolvedValueOnce({ - configFile: iniDataInConfig, - credentialsFile: iniDataInCredentials, - }); - (getProfileName as jest.Mock).mockReturnValueOnce(profile ?? "default"); - return expect(fromSharedConfigFiles(configGetter, { profile, preferredFile })()).rejects.toMatchObject( - getCredentialsProviderError(profile ?? "default", configGetter) - ); - }); - }); - - it("rejects if getter throws", () => { - const message = "Cannot load config"; - const failGetter = () => { - throw new Error(message); - }; - (loadSharedConfigFiles as jest.Mock).mockResolvedValueOnce({ - configFile: {}, - credentialsFile: {}, - }); - return expect(fromSharedConfigFiles(failGetter)()).rejects.toMatchObject(new CredentialsProviderError(message)); - }); - }); - - describe("profile", () => { - const loadedConfigData = { - configFile: { - default: { [configKey]: "configFileDefault" }, - foo: { [configKey]: "configFileFoo" }, - }, - credentialsFile: { - default: { [configKey]: "credentialsFileDefault" }, - }, - }; - - beforeEach(() => { - (loadSharedConfigFiles as jest.Mock).mockResolvedValueOnce(loadedConfigData); - }); - - it.each(["foo", "default"])("returns config value from %s profile", (profile) => { - (getProfileName as jest.Mock).mockReturnValueOnce(profile); - return expect(fromSharedConfigFiles(configGetter)()).resolves.toBe( - loadedConfigData.configFile[profile][configKey] - ); - }); - }); -}); diff --git a/packages/node-config-provider/src/fromSharedConfigFiles.ts b/packages/node-config-provider/src/fromSharedConfigFiles.ts deleted file mode 100644 index 144980c5e2d6..000000000000 --- a/packages/node-config-provider/src/fromSharedConfigFiles.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { CredentialsProviderError } from "@aws-sdk/property-provider"; -import { getProfileName, loadSharedConfigFiles, SourceProfileInit } from "@aws-sdk/shared-ini-file-loader"; -import { Profile, Provider } from "@aws-sdk/types"; - -export interface SharedConfigInit extends SourceProfileInit { - /** - * The preferred shared ini file to load the config. "config" option refers to - * the shared config file(defaults to `~/.aws/config`). "credentials" option - * refers to the shared credentials file(defaults to `~/.aws/credentials`) - */ - preferredFile?: "config" | "credentials"; -} - -export type GetterFromConfig = (profile: Profile) => T | undefined; - -/** - * Get config value from the shared config files with inferred profile name. - */ -export const fromSharedConfigFiles = - ( - configSelector: GetterFromConfig, - { preferredFile = "config", ...init }: SharedConfigInit = {} - ): Provider => - async () => { - const profile = getProfileName(init); - const { configFile, credentialsFile } = await loadSharedConfigFiles(init); - - const profileFromCredentials = credentialsFile[profile] || {}; - const profileFromConfig = configFile[profile] || {}; - const mergedProfile = - preferredFile === "config" - ? { ...profileFromCredentials, ...profileFromConfig } - : { ...profileFromConfig, ...profileFromCredentials }; - - try { - const configValue = configSelector(mergedProfile); - if (configValue === undefined) { - throw new Error(); - } - return configValue; - } catch (e) { - throw new CredentialsProviderError( - e.message || - `Cannot load config for profile ${profile} in SDK configuration files with getter: ${configSelector}` - ); - } - }; diff --git a/packages/node-config-provider/src/fromStatic.spec.ts b/packages/node-config-provider/src/fromStatic.spec.ts deleted file mode 100644 index 7adc40e0f410..000000000000 --- a/packages/node-config-provider/src/fromStatic.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { fromStatic as convertToProvider } from "@aws-sdk/property-provider"; - -import { fromStatic } from "./fromStatic"; - -jest.mock("@aws-sdk/property-provider", () => ({ - fromStatic: jest.fn(), -})); - -describe("fromStatic", () => { - const value = "default"; - it("should convert static values to provider", async () => { - (convertToProvider as jest.Mock).mockReturnValue(value); - fromStatic(value); - expect(convertToProvider as jest.Mock).toHaveBeenCalledWith(value); - }); - - it("should call the getter function", async () => { - const getter = jest.fn().mockReturnValue(value); - const config = fromStatic(getter); - expect(await config()).toBe(value); - expect(getter).toHaveBeenCalled(); - }); - - it("should call the async provider function", async () => { - const getter = jest.fn().mockResolvedValue(value); - const config = fromStatic(getter); - expect(await config()).toBe(value); - expect(getter).toHaveBeenCalled(); - }); -}); diff --git a/packages/node-config-provider/src/fromStatic.ts b/packages/node-config-provider/src/fromStatic.ts deleted file mode 100644 index a302d7a26b7c..000000000000 --- a/packages/node-config-provider/src/fromStatic.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { fromStatic as convertToProvider } from "@aws-sdk/property-provider"; -import { Provider } from "@aws-sdk/types"; - -export type FromStaticConfig = T | (() => T) | Provider; -type Getter = (() => T) | Provider; -const isFunction = (func: FromStaticConfig): func is Getter => typeof func === "function"; - -export const fromStatic = (defaultValue: FromStaticConfig): Provider => - isFunction(defaultValue) ? async () => await defaultValue() : convertToProvider(defaultValue); diff --git a/packages/node-config-provider/src/index.ts b/packages/node-config-provider/src/index.ts index 2d035d911106..a364d290a18a 100644 --- a/packages/node-config-provider/src/index.ts +++ b/packages/node-config-provider/src/index.ts @@ -1 +1 @@ -export * from "./configLoader"; +export * from "@smithy/node-config-provider"; diff --git a/packages/node-http-handler/fixtures/test-server-cert.pem b/packages/node-http-handler/fixtures/test-server-cert.pem deleted file mode 100644 index 86a72fd48aae..000000000000 --- a/packages/node-http-handler/fixtures/test-server-cert.pem +++ /dev/null @@ -1,26 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEWjCCAkICCQDK848c0wG7ajANBgkqhkiG9w0BAQsFADBvMQswCQYDVQQGEwJV -UzETMBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTElMCMGA1UE -CgwcTWVzc2FnZSBWYWxpZGF0b3IgVW5pdCBUZXN0czESMBAGA1UEAwwJbG9jYWxo -b3N0MB4XDTE3MDYyMzAxMTQzOFoXDTIzMDEzMTAxMTQzOFowbzELMAkGA1UEBhMC -VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxJTAjBgNV -BAoMHE1lc3NhZ2UgVmFsaWRhdG9yIFVuaXQgVGVzdHMxEjAQBgNVBAMMCWxvY2Fs -aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANknobw2ZghFY9yE -Ork5dSlW2giIk2Sb/0GajCXNc1ajVkn1R5e5WNLYWALFRyHmtvWFj6Rut9pQ4WOk -OKYsaWvNlB89CuvKDOZAF02BiTJuXsb5+jHIhg2uuEKS/bEZG27FFZZMLAIeMpdN -Ro+02gKH1DXp6xa3nHkwR00b+0traTwyvvJQsaKHmEHClKwom1i8A60l2Ctm+K2I -io7uLafVX7Xufmqhgxyn+ZSPe/iYgqSrh68gc/OkPuaakMEx72pfqK1wAZz5NWSn -6MFi/mfXmvbL7hfCwThABAQ1I2codGyq7Ax1mMeC/bYYGAIPRoUbsx0jkjzO64gw -NBs+WJECAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAJkb1qMVICfH0cx+loEm8H4vp -v2gt2iinFprYbKuzsYIysc8fVrz7Fcs2mG3Co0uXzddNAzd1ZwPO5vWyU7nu8J0L -LSx2u7prAl4Jflr/kwlFt2f/ParTBgdoVSM4sI2VgL/B89fKcC3C24lEU5dPsntg -cbBhlhDtOpn0BVhEuVVXFMMmqthkrzZjRrsZZ8uwR8tbJWXh6peMPCD/86NFrWgK -givJOrWmraE6MbIo1AUEj7wTK7+viXmzKkYpAj/pJI2Duy4Xbx4t+ry2//rByc4G -rJpsHnQmN7zM+AvMClQyyg+F0BXRBr71cbrbCko59MxWwHdT7+Z7y0fc9sKhVuQO -RkJ2p4+0ll1JLE39i6Q/cGBr65MqeSx6Feo6rlQbW8qD7YLZrFHmxHKHD1kLx9h1 -agfawgi2B++qHFdMe5sxZ2cSneWmFIiwTMWbdKIVmhPx+tuYh+QyQScyoFCoy3c4 -WpKWjjg+wfwDgf41rfvlO0xyG1VZ1bVKSRkiQjdXZ+/4DFrNeKFDpiexSio7dgJZ -JR+wzPSFzGDlSv96gf+cKNgJA1Yw5r2Y3zGN65tFpMm4qLDR6R6ajy8IRMcCqGUq -BxAexvuxslcc0pj3e9vUWkp3Ky5u+xNQ1EiS6VMbWEcJYjNInEAlTdS+V5uiCDh3 -aLzuR8HtZN016piziIU= ------END CERTIFICATE----- diff --git a/packages/node-http-handler/fixtures/test-server-key.pem b/packages/node-http-handler/fixtures/test-server-key.pem deleted file mode 100644 index 871136fa1086..000000000000 --- a/packages/node-http-handler/fixtures/test-server-key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA2SehvDZmCEVj3IQ6uTl1KVbaCIiTZJv/QZqMJc1zVqNWSfVH -l7lY0thYAsVHIea29YWPpG632lDhY6Q4pixpa82UHz0K68oM5kAXTYGJMm5exvn6 -MciGDa64QpL9sRkbbsUVlkwsAh4yl01Gj7TaAofUNenrFreceTBHTRv7S2tpPDK+ -8lCxooeYQcKUrCibWLwDrSXYK2b4rYiKju4tp9Vfte5+aqGDHKf5lI97+JiCpKuH -ryBz86Q+5pqQwTHval+orXABnPk1ZKfowWL+Z9ea9svuF8LBOEAEBDUjZyh0bKrs -DHWYx4L9thgYAg9GhRuzHSOSPM7riDA0Gz5YkQIDAQABAoIBAQDPmUjQgvzmOVgv -j6YIP3rXa3WDpPWrwEq1sAb9eL0j/YDXsYqg7QuSfksdUvYe3c7ZR7c8DrDrIFlp -Ba02h8y8x8ssVhIjuoS8dlcQvJ6pvMQU2xQqFba6S+dRle68KPGF4xoxFl8YI0Bg -Tvr/FXk55Bqm9BrQG/aWEOaJPA/wV1tq4d/Sv+pLJozM7ejzl0/QRV1p2dnvKwvO -kO48G/Y+k8DXnwIR5qcGhAZP7wxb2cvzWEQwuBDNKedCNAInjjakXJt4bH+vYAE6 -RXlzZnQC2LRMKomlCIcaJeINlhlCX/HJPYQrz8Zav6WTAYvolHqW9HRBqp0mG7iZ -O6NmLvOVAoGBAO6CHnsHKUo2rlf5E68u8HlbxPJcToOt+APLymvJOcjGnrgLKo01 -KqMMyUVVXyGfpYcN2Bxvk92BTQ71xt4FzqaNn+Ag/NRtHi/ecYnPrH3X+Dd0WRd/ -VFX4cGdqd0JXNOMLhKPsk+qO9h9lAoBiDMM2B6pyN4P8mLs3VXU1QrG/AoGBAOkU -nIWzWTMqShkv6CmE2dBkeZaDq3vsgdvGYkaluhJ4nKoMM2GCPtcVmSpKna5GNcsx -rAkxSbnz+WEUGJ+XQxP2bsZsfUbSsBqYOYSqqGVJaBsIXR4s+iB27l/XAKDuPsYr -eYH7HbDf6afxEz5Bbf2E8ZvEZQ0M+UrPRB75IOmvAoGBAL5G2IJWCD7IuPY+I9IS -pI5tBAZGVez/kWmV33t2Ib9nlaBGaEAXNli2Dqxdm3N7pdbE2LB244RHb26L7Yeb -Im4FdpKcPphKJVcTI4lKQNZ0wfWbwKfaUTH07dfTPCmU4QBxY/RS/P6X5wrMzt4V -WxExvZPhYyDNGBvj3S2QvBCJAoGBAIZmKjM2TbMhKYUIiNiYEHkH1syhtBpLMD4o -ULboDTllbwDm9CG/1rhzbdRjHjVFqvM1+zt5vkeJlT0TN3ee40D5krq8CCj0iDNt -n40OUvfEslEUK42g5cIekimVcnlZp7zhiLkYsfAxzSvX6P62/9N1+1OUlahG2OD4 -TxGFGiNlAoGAE6S7R3GJyBUrgEd7ViOFGYkArmZnYgEvGgZ8IuS17BXFZdENdzjz -b78WsPgxyRSMttKnmcDEErdtfVNyS2tFNSfTRpHHFxM8x2Ot/mw0krUSpXzMGc1s -i6TltbVl1gSrhE1P8Kpuw4farJaP8M6P5UMU/HpPCO38EmnvKvxdTgA= ------END RSA PRIVATE KEY----- diff --git a/packages/node-http-handler/jest.config.js b/packages/node-http-handler/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/node-http-handler/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/node-http-handler/package.json b/packages/node-http-handler/package.json index 1608277632c8..b960c85ed6ab 100644 --- a/packages/node-http-handler/package.json +++ b/packages/node-http-handler/package.json @@ -11,7 +11,7 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "extract:docs": "api-extractor run --local", - "test": "jest --coverage" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", @@ -23,10 +23,7 @@ "module": "./dist-es/index.js", "types": "./dist-types/index.d.ts", "dependencies": { - "@aws-sdk/abort-controller": "*", - "@aws-sdk/protocol-http": "*", - "@aws-sdk/querystring-builder": "*", - "@aws-sdk/types": "*", + "@smithy/node-http-handler": "^1.0.2", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/node-http-handler/src/constants.ts b/packages/node-http-handler/src/constants.ts deleted file mode 100644 index 627b84faccb8..000000000000 --- a/packages/node-http-handler/src/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Node.js system error codes that indicate timeout. - * @deprecated use NODEJS_TIMEOUT_ERROR_CODES from @aws-sdk/service-error-classification/constants - */ -export const NODEJS_TIMEOUT_ERROR_CODES = ["ECONNRESET", "EPIPE", "ETIMEDOUT"]; diff --git a/packages/node-http-handler/src/get-transformed-headers.ts b/packages/node-http-handler/src/get-transformed-headers.ts deleted file mode 100644 index 7225b9746daf..000000000000 --- a/packages/node-http-handler/src/get-transformed-headers.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { HeaderBag } from "@aws-sdk/types"; -import { IncomingHttpHeaders } from "http2"; - -const getTransformedHeaders = (headers: IncomingHttpHeaders) => { - const transformedHeaders: HeaderBag = {}; - - for (const name of Object.keys(headers)) { - const headerValues = headers[name]; - transformedHeaders[name] = Array.isArray(headerValues) ? headerValues.join(",") : headerValues; - } - - return transformedHeaders; -}; - -export { getTransformedHeaders }; diff --git a/packages/node-http-handler/src/index.spec.ts b/packages/node-http-handler/src/index.spec.ts deleted file mode 100644 index 73ec362f4ad4..000000000000 --- a/packages/node-http-handler/src/index.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NodeHttpHandler } from "./index"; - -describe("index", () => { - it("exports NodeHttpHandler", () => { - expect(typeof NodeHttpHandler).toBe("function"); - }); -}); diff --git a/packages/node-http-handler/src/index.ts b/packages/node-http-handler/src/index.ts index 09c0b9a5667a..0bd37ece324e 100644 --- a/packages/node-http-handler/src/index.ts +++ b/packages/node-http-handler/src/index.ts @@ -1,3 +1 @@ -export * from "./node-http-handler"; -export * from "./node-http2-handler"; -export * from "./stream-collector"; +export * from "@smithy/node-http-handler"; diff --git a/packages/node-http-handler/src/node-http-handler.spec.ts b/packages/node-http-handler/src/node-http-handler.spec.ts deleted file mode 100644 index 7da3453f1687..000000000000 --- a/packages/node-http-handler/src/node-http-handler.spec.ts +++ /dev/null @@ -1,643 +0,0 @@ -import { AbortController } from "@aws-sdk/abort-controller"; -import { HttpRequest } from "@aws-sdk/protocol-http"; -import http, { Server as HttpServer } from "http"; -import https, { Server as HttpsServer } from "https"; -import { AddressInfo } from "net"; - -import { NodeHttpHandler } from "./node-http-handler"; -import { ReadFromBuffers } from "./readable.mock"; -import { - createContinueResponseFunction, - createMockHttpServer, - createMockHttpsServer, - createResponseFunction, -} from "./server.mock"; - -describe("NodeHttpHandler", () => { - describe("constructor and #handle", () => { - let hRequestSpy: jest.SpyInstance; - let hsRequestSpy: jest.SpyInstance; - const randomMaxSocket = Math.round(Math.random() * 50) + 1; - const mockRequestImpl = (protocol: string) => (_options, cb) => { - cb({ - statusCode: 200, - body: "body", - headers: {}, - protocol, - }); - return new http.ClientRequest({ ..._options, protocol }); - }; - - beforeEach(() => { - hRequestSpy = jest.spyOn(http, "request").mockImplementation(mockRequestImpl("http:")); - hsRequestSpy = jest.spyOn(https, "request").mockImplementation(mockRequestImpl("https:")); - }); - - afterEach(() => { - hRequestSpy.mockRestore(); - hsRequestSpy.mockRestore(); - }); - describe("constructor", () => { - it.each([ - ["empty", undefined], - ["a provider", async () => {}], - ])("sets keepAlive=true by default when input is %s", async (_, option) => { - const nodeHttpHandler = new NodeHttpHandler(option); - await nodeHttpHandler.handle({} as any); - expect(hRequestSpy.mock.calls[0][0]?.agent.keepAlive).toEqual(true); - }); - - it.each([ - ["empty", undefined], - ["a provider", async () => {}], - ])("sets maxSockets=50 by default when input is %s", async (_, option) => { - const nodeHttpHandler = new NodeHttpHandler(option); - await nodeHttpHandler.handle({} as any); - expect(hRequestSpy.mock.calls[0][0]?.agent.maxSockets).toEqual(50); - }); - - it.each([ - ["an options hash", { httpAgent: new http.Agent({ keepAlive: false, maxSockets: randomMaxSocket }) }], - [ - "a provider", - async () => ({ - httpAgent: new http.Agent({ keepAlive: false, maxSockets: randomMaxSocket }), - }), - ], - ])("sets httpAgent when input is %s", async (_, option) => { - const nodeHttpHandler = new NodeHttpHandler(option); - await nodeHttpHandler.handle({ protocol: "http:", headers: {}, method: "GET", hostname: "localhost" } as any); - expect(hRequestSpy.mock.calls[0][0]?.agent.keepAlive).toEqual(false); - expect(hRequestSpy.mock.calls[0][0]?.agent.maxSockets).toEqual(randomMaxSocket); - }); - - it.each([ - ["an option hash", { httpsAgent: new https.Agent({ keepAlive: true, maxSockets: randomMaxSocket }) }], - [ - "a provider", - async () => ({ - httpsAgent: new https.Agent({ keepAlive: true, maxSockets: randomMaxSocket }), - }), - ], - ])("sets httpsAgent when input is %s", async (_, option) => { - const nodeHttpHandler = new NodeHttpHandler(option); - await nodeHttpHandler.handle({ protocol: "https:" } as any); - expect(hsRequestSpy.mock.calls[0][0]?.agent.keepAlive).toEqual(true); - expect(hsRequestSpy.mock.calls[0][0]?.agent.maxSockets).toEqual(randomMaxSocket); - }); - }); - - describe("#handle", () => { - it("should only generate a single config when the config provider is async and it is not ready yet", async () => { - let providerInvokedCount = 0; - let providerResolvedCount = 0; - const slowConfigProvider = async () => { - providerInvokedCount += 1; - await new Promise((r) => setTimeout(r, 15)); - providerResolvedCount += 1; - return { - connectionTimeout: 12345, - socketTimeout: 12345, - httpAgent: void 0, - httpsAgent: void 0, - }; - }; - - const nodeHttpHandler = new NodeHttpHandler(slowConfigProvider); - - const promises = Promise.all( - Array.from({ length: 20 }).map(() => nodeHttpHandler.handle({} as unknown as HttpRequest)) - ); - - expect(providerInvokedCount).toBe(1); - expect(providerResolvedCount).toBe(0); - await promises; - expect(providerInvokedCount).toBe(1); - expect(providerResolvedCount).toBe(1); - }); - - it("sends requests to the right url", async () => { - const nodeHttpHandler = new NodeHttpHandler({}); - const httpRequest = { - protocol: "http:", - username: "username", - password: "password", - hostname: "host", - port: 1234, - path: "/some/path", - query: { - some: "query", - }, - fragment: "fragment", - }; - await nodeHttpHandler.handle(httpRequest as any); - expect(hRequestSpy.mock.calls[0][0]?.auth).toEqual("username:password"); - expect(hRequestSpy.mock.calls[0][0]?.host).toEqual("host"); - expect(hRequestSpy.mock.calls[0][0]?.port).toEqual(1234); - expect(hRequestSpy.mock.calls[0][0]?.path).toEqual("/some/path?some=query#fragment"); - }); - }); - }); - - describe("http", () => { - let mockHttpServer: HttpServer; - beforeAll(() => { - mockHttpServer = createMockHttpServer().listen(54321); - }); - - afterEach(() => { - mockHttpServer.removeAllListeners("request"); - mockHttpServer.removeAllListeners("checkContinue"); - }); - - afterAll(() => { - mockHttpServer.close(); - }); - - it("has metadata", () => { - const nodeHttpHandler = new NodeHttpHandler(); - expect(nodeHttpHandler.metadata.handlerProtocol).toContain("http/1.1"); - }); - - it("can send http requests", async () => { - const mockResponse = { - statusCode: 200, - statusText: "OK", - headers: {}, - body: "test", - }; - mockHttpServer.addListener("request", createResponseFunction(mockResponse)); - const nodeHttpHandler = new NodeHttpHandler(); - - const { response } = await nodeHttpHandler.handle( - new HttpRequest({ - hostname: "localhost", - method: "GET", - port: (mockHttpServer.address() as AddressInfo).port, - protocol: "http:", - path: "/", - headers: {}, - }), - {} - ); - - expect(response.statusCode).toEqual(mockResponse.statusCode); - expect(response.reason).toEqual(mockResponse.statusText); - expect(response.headers).toBeDefined(); - expect(response.headers).toMatchObject(mockResponse.headers); - expect(response.body).toBeDefined(); - }); - - it("can send requests with bodies", async () => { - const body = Buffer.from("test"); - const mockResponse = { - statusCode: 200, - headers: {}, - }; - mockHttpServer.addListener("request", createResponseFunction(mockResponse)); - const spy = jest.spyOn(http, "request").mockImplementationOnce(() => { - const calls = spy.mock.calls; - const currentIndex = calls.length - 1; - return http.request(calls[currentIndex][0], calls[currentIndex][1]); - }); - - const nodeHttpHandler = new NodeHttpHandler(); - const { response } = await nodeHttpHandler.handle( - new HttpRequest({ - hostname: "localhost", - method: "PUT", - port: (mockHttpServer.address() as AddressInfo).port, - protocol: "http:", - path: "/", - headers: {}, - body, - }), - {} - ); - - expect(response.statusCode).toEqual(mockResponse.statusCode); - expect(response.headers).toBeDefined(); - expect(response.headers).toMatchObject(mockResponse.headers); - }); - - it("can handle expect 100-continue", async () => { - const body = Buffer.from("test"); - const mockResponse = { - statusCode: 200, - headers: {}, - }; - - mockHttpServer.addListener("checkContinue", createContinueResponseFunction(mockResponse)); - let endSpy: jest.SpyInstance; - let continueWasTriggered = false; - const spy = jest.spyOn(http, "request").mockImplementationOnce(() => { - const calls = spy.mock.calls; - const currentIndex = calls.length - 1; - const request = http.request(calls[currentIndex][0], calls[currentIndex][1]); - request.on("continue", () => { - continueWasTriggered = true; - }); - endSpy = jest.spyOn(request, "end"); - - return request; - }); - - const nodeHttpHandler = new NodeHttpHandler(); - const { response } = await nodeHttpHandler.handle( - new HttpRequest({ - hostname: "localhost", - method: "PUT", - port: (mockHttpServer.address() as AddressInfo).port, - protocol: "http:", - path: "/", - headers: { - Expect: "100-continue", - }, - body, - }), - {} - ); - - expect(response.statusCode).toEqual(mockResponse.statusCode); - expect(response.headers).toBeDefined(); - expect(response.headers).toMatchObject(mockResponse.headers); - expect(endSpy!.mock.calls.length).toBe(1); - expect(endSpy!.mock.calls[0][0]).toStrictEqual(body); - expect(continueWasTriggered).toBe(true); - }); - - it("can send requests with streaming bodies", async () => { - const body = new ReadFromBuffers({ - buffers: [Buffer.from("t"), Buffer.from("e"), Buffer.from("s"), Buffer.from("t")], - }); - const inputBodySpy = jest.spyOn(body, "pipe"); - const mockResponse = { - statusCode: 200, - headers: {}, - }; - mockHttpServer.addListener("request", createResponseFunction(mockResponse)); - const nodeHttpHandler = new NodeHttpHandler(); - - const { response } = await nodeHttpHandler.handle( - new HttpRequest({ - hostname: "localhost", - method: "PUT", - port: (mockHttpServer.address() as AddressInfo).port, - protocol: "http:", - path: "/", - headers: {}, - body, - }), - {} - ); - - expect(response.statusCode).toEqual(mockResponse.statusCode); - expect(response.headers).toBeDefined(); - expect(response.headers).toMatchObject(mockResponse.headers); - expect(inputBodySpy.mock.calls.length).toBeTruthy(); - }); - - it("can send requests with Uint8Array bodies", async () => { - const body = Buffer.from([0, 1, 2, 3]); - const mockResponse = { - statusCode: 200, - headers: {}, - }; - mockHttpServer.addListener("request", createResponseFunction(mockResponse)); - let endSpy: jest.SpyInstance; - const spy = jest.spyOn(http, "request").mockImplementationOnce(() => { - const calls = spy.mock.calls; - const currentIndex = calls.length - 1; - const request = http.request(calls[currentIndex][0], calls[currentIndex][1]); - endSpy = jest.spyOn(request, "end"); - return request; - }); - - const nodeHttpHandler = new NodeHttpHandler(); - const { response } = await nodeHttpHandler.handle( - new HttpRequest({ - hostname: "localhost", - method: "PUT", - port: (mockHttpServer.address() as AddressInfo).port, - protocol: "http:", - path: "/", - headers: {}, - body, - }), - {} - ); - - expect(response.statusCode).toEqual(mockResponse.statusCode); - expect(response.headers).toBeDefined(); - expect(response.headers).toMatchObject(mockResponse.headers); - expect(endSpy!.mock.calls.length).toBe(1); - expect(endSpy!.mock.calls[0][0]).toStrictEqual(body); - }); - }); - - describe("https", () => { - const mockHttpsServer: HttpsServer = createMockHttpsServer().listen(54322); - - /*beforeEach(() => { - // Setting the NODE_TLS_REJECT_UNAUTHORIZED will allow the unconfigurable - // HTTPS client in getCertificate to skip cert validation, which the - // self-signed cert used for this test's server would fail. The variable - // will be reset to its original value at the end of the test. - process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; - });*/ - - afterEach(() => { - mockHttpsServer.removeAllListeners("request"); - mockHttpsServer.removeAllListeners("checkContinue"); - //process.env.NODE_TLS_REJECT_UNAUTHORIZED = rejectUnauthorizedEnv; - }); - - afterAll(() => { - mockHttpsServer.close(); - }); - /*it("can send https requests", async () => { - const mockResponse = { - statusCode: 200, - headers: {}, - body: "test" - }; - mockHttpsServer.addListener( - "request", - createResponseFunction(mockResponse) - ); - const nodeHttpHandler = new NodeHttpHandler(); - - let { response } = await nodeHttpHandler.handle( - new HttpRequest({ - hostname: "localhost", - method: "GET", - port: (mockHttpsServer.address() as AddressInfo).port, - protocol: "https:", - path: "/", - headers: {} - }), - {} - ); - - expect(response.statusCode).toEqual(mockResponse.statusCode); - expect(response.headers).toBeDefined(); - expect(response.headers).toMatchObject(mockResponse.headers); - expect(response.body).toBeDefined(); - }); - - it("can send requests with bodies", async () => { - const body = Buffer.from("test"); - const mockResponse = { - statusCode: 200, - headers: {} - }; - mockHttpsServer.addListener( - "request", - createResponseFunction(mockResponse) - ); - const spy = jest.spyOn(https, "request").mockImplementationOnce(() => { - let calls = spy.mock.calls; - let currentIndex = calls.length - 1; - return https.request(calls[currentIndex][0], calls[currentIndex][1]); - }); - - const nodeHttpHandler = new NodeHttpHandler(); - let { response } = await nodeHttpHandler.handle( - new HttpRequest({ - hostname: "localhost", - method: "PUT", - port: (mockHttpsServer.address() as AddressInfo).port, - protocol: "https:", - path: "/", - headers: {}, - body - }), - {} - ); - - expect(response.statusCode).toEqual(mockResponse.statusCode); - expect(response.headers).toBeDefined(); - expect(response.headers).toMatchObject(mockResponse.headers); - }); - - it("can handle expect 100-continue", async () => { - const body = Buffer.from("test"); - const mockResponse = { - statusCode: 200, - headers: {} - }; - - mockHttpsServer.addListener( - "checkContinue", - createContinueResponseFunction(mockResponse) - ); - let endSpy: jest.SpyInstance; - let continueWasTriggered = false; - const spy = jest.spyOn(https, "request").mockImplementationOnce(() => { - let calls = spy.mock.calls; - let currentIndex = calls.length - 1; - const request = https.request( - calls[currentIndex][0], - calls[currentIndex][1] - ); - request.on("continue", () => { - continueWasTriggered = true; - }); - endSpy = jest.spyOn(request, "end"); - - return request; - }); - - const nodeHttpHandler = new NodeHttpHandler(); - let response = await nodeHttpHandler.handle( - { - hostname: "localhost", - method: "PUT", - port: (mockHttpServer.address() as AddressInfo).port, - protocol: "https:", - path: "/", - headers: { - Expect: "100-continue" - }, - body - }, - {} - ); - - expect(response.statusCode).toEqual(mockResponse.statusCode); - expect(response.headers).toBeDefined(); - expect(response.headers).toMatchObject(mockResponse.headers); - expect(endSpy!.mock.calls.length).toBe(1); - expect(endSpy!.mock.calls[0][0]).toBe(body); - expect(continueWasTriggered).toBe(true); - }); - - it("can send requests with streaming bodies", async () => { - const body = new ReadFromBuffers({ - buffers: [ - Buffer.from("t"), - Buffer.from("e"), - Buffer.from("s"), - Buffer.from("t") - ] - }); - let inputBodySpy = jest.spyOn(body, "pipe"); - const mockResponse = { - statusCode: 200, - headers: {} - }; - mockHttpsServer.addListener( - "request", - createResponseFunction(mockResponse) - ); - const nodeHttpHandler = new NodeHttpHandler(); - - let { response } = await nodeHttpHandler.handle( - new HttpRequest({ - hostname: "localhost", - method: "PUT", - port: (mockHttpsServer.address() as AddressInfo).port, - protocol: "https:", - path: "/", - headers: {}, - body - }), - {} - ); - - expect(response.statusCode).toEqual(mockResponse.statusCode); - expect(response.headers).toBeDefined(); - expect(response.headers).toMatchObject(mockResponse.headers); - expect(inputBodySpy.mock.calls.length).toBeTruthy(); - });*/ - - it("rejects if the request encounters an error", async () => { - const mockResponse = { - statusCode: 200, - headers: {}, - body: "test", - }; - mockHttpsServer.addListener("request", createResponseFunction(mockResponse)); - const nodeHttpHandler = new NodeHttpHandler(); - - await expect( - nodeHttpHandler.handle( - new HttpRequest({ - hostname: "localhost", - method: "GET", - port: (mockHttpsServer.address() as AddressInfo).port, - protocol: "fake:", // trigger a request error - path: "/", - headers: {}, - }), - {} - ) - ).rejects.toHaveProperty("message"); - }); - - it("will not make request if already aborted", async () => { - const mockResponse = { - statusCode: 200, - headers: {}, - body: "test", - }; - mockHttpsServer.addListener("request", createResponseFunction(mockResponse)); - const spy = jest.spyOn(https, "request").mockImplementationOnce(() => { - const calls = spy.mock.calls; - const currentIndex = calls.length - 1; - return https.request(calls[currentIndex][0], calls[currentIndex][1]); - }); - // clear data held from previous tests - spy.mockClear(); - const nodeHttpHandler = new NodeHttpHandler(); - - await expect( - nodeHttpHandler.handle( - new HttpRequest({ - hostname: "localhost", - method: "GET", - port: (mockHttpsServer.address() as AddressInfo).port, - protocol: "https:", - path: "/", - headers: {}, - }), - { - abortSignal: { - aborted: true, - onabort: null, - }, - } - ) - ).rejects.toHaveProperty("name", "AbortError"); - - expect(spy.mock.calls.length).toBe(0); - }); - - it(`won't throw uncatchable error in writeRequestBody`, async () => { - const nodeHttpHandler = new NodeHttpHandler(); - - await expect( - nodeHttpHandler.handle( - new HttpRequest({ - hostname: "localhost", - method: "GET", - port: (mockHttpsServer.address() as AddressInfo).port, - protocol: "https:", - path: "/", - headers: {}, - body: {}, - }) - ) - ).rejects.toHaveProperty("name", "TypeError"); - }); - - it("will destroy the request when aborted", async () => { - const mockResponse = { - statusCode: 200, - headers: {}, - body: "test", - }; - mockHttpsServer.addListener("request", createResponseFunction(mockResponse)); - let httpRequest: http.ClientRequest; - let reqAbortSpy: any; - const spy = jest.spyOn(https, "request").mockImplementationOnce(() => { - const calls = spy.mock.calls; - const currentIndex = calls.length - 1; - httpRequest = https.request(calls[currentIndex][0], calls[currentIndex][1]); - reqAbortSpy = jest.spyOn(httpRequest, "abort"); - return httpRequest; - }); - const nodeHttpHandler = new NodeHttpHandler(); - const abortController = new AbortController(); - - setTimeout(() => { - abortController.abort(); - }, 0); - - await expect( - nodeHttpHandler.handle( - new HttpRequest({ - hostname: "localhost", - method: "GET", - port: (mockHttpsServer.address() as AddressInfo).port, - protocol: "https:", - path: "/", - headers: {}, - }), - { - abortSignal: abortController.signal, - } - ) - ).rejects.toHaveProperty("name", "AbortError"); - - expect(reqAbortSpy.mock.calls.length).toBe(1); - }); - }); - - describe("#destroy", () => { - it("should be callable and return nothing", () => { - const nodeHttpHandler = new NodeHttpHandler(); - expect(nodeHttpHandler.destroy()).toBeUndefined(); - }); - }); -}); diff --git a/packages/node-http-handler/src/node-http-handler.ts b/packages/node-http-handler/src/node-http-handler.ts deleted file mode 100644 index 288f863a6186..000000000000 --- a/packages/node-http-handler/src/node-http-handler.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { HttpHandler, HttpRequest, HttpResponse } from "@aws-sdk/protocol-http"; -import { buildQueryString } from "@aws-sdk/querystring-builder"; -import { HttpHandlerOptions, Provider } from "@aws-sdk/types"; -import { Agent as hAgent, request as hRequest } from "http"; -import { Agent as hsAgent, request as hsRequest, RequestOptions } from "https"; - -import { NODEJS_TIMEOUT_ERROR_CODES } from "./constants"; -import { getTransformedHeaders } from "./get-transformed-headers"; -import { setConnectionTimeout } from "./set-connection-timeout"; -import { setSocketKeepAlive } from "./set-socket-keep-alive"; -import { setSocketTimeout } from "./set-socket-timeout"; -import { writeRequestBody } from "./write-request-body"; - -/** - * Represents the http options that can be passed to a node http client. - */ -export interface NodeHttpHandlerOptions { - /** - * The maximum time in milliseconds that the connection phase of a request - * may take before the connection attempt is abandoned. - * - * Defaults to 0, which disables the timeout. - */ - connectionTimeout?: number; - - /** - * The number of milliseconds a request can take before automatically being terminated. - * Defaults to 0, which disables the timeout. - */ - requestTimeout?: number; - - /** - * @deprecated Use {@link requestTimeout} - * - * The maximum time in milliseconds that a socket may remain idle before it - * is closed. - */ - socketTimeout?: number; - - httpAgent?: hAgent; - httpsAgent?: hsAgent; -} - -interface ResolvedNodeHttpHandlerConfig { - requestTimeout?: number; - connectionTimeout?: number; - httpAgent: hAgent; - httpsAgent: hsAgent; -} - -export const DEFAULT_REQUEST_TIMEOUT = 0; - -export class NodeHttpHandler implements HttpHandler { - private config?: ResolvedNodeHttpHandlerConfig; - private readonly configProvider: Promise; - - // Node http handler is hard-coded to http/1.1: https://github.com/nodejs/node/blob/ff5664b83b89c55e4ab5d5f60068fb457f1f5872/lib/_http_server.js#L286 - public readonly metadata = { handlerProtocol: "http/1.1" }; - - constructor(options?: NodeHttpHandlerOptions | Provider) { - this.configProvider = new Promise((resolve, reject) => { - if (typeof options === "function") { - options() - .then((_options) => { - resolve(this.resolveDefaultConfig(_options)); - }) - .catch(reject); - } else { - resolve(this.resolveDefaultConfig(options)); - } - }); - } - - private resolveDefaultConfig(options?: NodeHttpHandlerOptions | void): ResolvedNodeHttpHandlerConfig { - const { requestTimeout, connectionTimeout, socketTimeout, httpAgent, httpsAgent } = options || {}; - const keepAlive = true; - const maxSockets = 50; - - return { - connectionTimeout, - requestTimeout: requestTimeout ?? socketTimeout, - httpAgent: httpAgent || new hAgent({ keepAlive, maxSockets }), - httpsAgent: httpsAgent || new hsAgent({ keepAlive, maxSockets }), - }; - } - - destroy(): void { - this.config?.httpAgent?.destroy(); - this.config?.httpsAgent?.destroy(); - } - - async handle(request: HttpRequest, { abortSignal }: HttpHandlerOptions = {}): Promise<{ response: HttpResponse }> { - if (!this.config) { - this.config = await this.configProvider; - } - return new Promise((_resolve, _reject) => { - let writeRequestBodyPromise: Promise | undefined = undefined; - const resolve = async (arg: { response: HttpResponse }) => { - await writeRequestBodyPromise; - _resolve(arg); - }; - const reject = async (arg: unknown) => { - await writeRequestBodyPromise; - _reject(arg); - }; - - if (!this.config) { - throw new Error("Node HTTP request handler config is not resolved"); - } - - // if the request was already aborted, prevent doing extra work - if (abortSignal?.aborted) { - const abortError = new Error("Request aborted"); - abortError.name = "AbortError"; - reject(abortError); - return; - } - - // determine which http(s) client to use - const isSSL = request.protocol === "https:"; - const queryString = buildQueryString(request.query || {}); - let auth = undefined; - if (request.username != null || request.password != null) { - const username = request.username ?? ""; - const password = request.password ?? ""; - auth = `${username}:${password}`; - } - let path = request.path; - if (queryString) { - path += `?${queryString}`; - } - if (request.fragment) { - path += `#${request.fragment}`; - } - const nodeHttpsOptions: RequestOptions = { - headers: request.headers, - host: request.hostname, - method: request.method, - path, - port: request.port, - agent: isSSL ? this.config.httpsAgent : this.config.httpAgent, - auth, - }; - - // create the http request - const requestFunc = isSSL ? hsRequest : hRequest; - - const req = requestFunc(nodeHttpsOptions, (res) => { - const httpResponse = new HttpResponse({ - statusCode: res.statusCode || -1, - reason: res.statusMessage, - headers: getTransformedHeaders(res.headers), - body: res, - }); - resolve({ response: httpResponse }); - }); - - req.on("error", (err: Error) => { - if (NODEJS_TIMEOUT_ERROR_CODES.includes((err as any).code)) { - reject(Object.assign(err, { name: "TimeoutError" })); - } else { - reject(err); - } - }); - - // wire-up any timeout logic - setConnectionTimeout(req, reject, this.config.connectionTimeout); - setSocketTimeout(req, reject, this.config.requestTimeout); - - // wire-up abort logic - if (abortSignal) { - abortSignal.onabort = () => { - // ensure request is destroyed - req.abort(); - const abortError = new Error("Request aborted"); - abortError.name = "AbortError"; - reject(abortError); - }; - } - - // Workaround for bug report in Node.js https://github.com/nodejs/node/issues/47137 - const httpAgent = nodeHttpsOptions.agent; - if (typeof httpAgent === "object" && "keepAlive" in httpAgent) { - setSocketKeepAlive(req, { - // @ts-expect-error keepAlive is not public on httpAgent. - keepAlive: (httpAgent as hAgent).keepAlive, - // @ts-expect-error keepAliveMsecs is not public on httpAgent. - keepAliveMsecs: (httpAgent as hAgent).keepAliveMsecs, - }); - } - - writeRequestBodyPromise = writeRequestBody(req, request, this.config.requestTimeout).catch(_reject); - }); - } -} diff --git a/packages/node-http-handler/src/node-http2-connection-manager.ts b/packages/node-http-handler/src/node-http2-connection-manager.ts deleted file mode 100644 index 18ed8e575183..000000000000 --- a/packages/node-http-handler/src/node-http2-connection-manager.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { RequestContext } from "@aws-sdk/types"; -import { ConnectConfiguration } from "@aws-sdk/types"; -import { ConnectionManager, ConnectionManagerConfiguration } from "@aws-sdk/types"; -import http2, { ClientHttp2Session } from "http2"; - -import { NodeHttp2ConnectionPool } from "./node-http2-connection-pool"; - -export class NodeHttp2ConnectionManager implements ConnectionManager { - constructor(config: ConnectionManagerConfiguration) { - this.config = config; - - if (this.config.maxConcurrency && this.config.maxConcurrency <= 0) { - throw new RangeError("maxConcurrency must be greater than zero."); - } - } - - private config: ConnectionManagerConfiguration; - - private readonly sessionCache: Map = new Map(); - - public lease(requestContext: RequestContext, connectionConfiguration: ConnectConfiguration): ClientHttp2Session { - const url = this.getUrlString(requestContext); - - const existingPool = this.sessionCache.get(url); - - if (existingPool) { - const existingSession = existingPool.poll(); - if (existingSession && !this.config.disableConcurrency) { - return existingSession; - } - } - - const session = http2.connect(url); - - if (this.config.maxConcurrency) { - session.settings({ maxConcurrentStreams: this.config.maxConcurrency }, (err) => { - if (err) { - throw new Error( - "Fail to set maxConcurrentStreams to " + - this.config.maxConcurrency + - "when creating new session for " + - requestContext.destination.toString() - ); - } - }); - } - - // AWS SDK does not expect server push streams, don't keep node alive without a request. - session.unref(); - - const destroySessionCb = () => { - session.destroy(); - this.deleteSession(url, session); - }; - session.on("goaway", destroySessionCb); - session.on("error", destroySessionCb); - session.on("frameError", destroySessionCb); - session.on("close", () => this.deleteSession(url, session)); - - if (connectionConfiguration.requestTimeout) { - session.setTimeout(connectionConfiguration.requestTimeout, destroySessionCb); - } - - const connectionPool = this.sessionCache.get(url) || new NodeHttp2ConnectionPool(); - - connectionPool.offerLast(session); - - this.sessionCache.set(url, connectionPool); - - return session; - } - - /** - * Delete a session from the connection pool. - * @param authority The authority of the session to delete. - * @param session The session to delete. - */ - public deleteSession(authority: string, session: ClientHttp2Session): void { - const existingConnectionPool = this.sessionCache.get(authority); - - if (!existingConnectionPool) { - return; - } - - if (!existingConnectionPool.contains(session)) { - return; - } - - existingConnectionPool.remove(session); - - this.sessionCache.set(authority, existingConnectionPool); - } - - public release(requestContext: RequestContext, session: ClientHttp2Session): void { - const cacheKey = this.getUrlString(requestContext); - this.sessionCache.get(cacheKey)?.offerLast(session); - } - - public destroy(): void { - for (const [key, connectionPool] of this.sessionCache) { - for (const session of connectionPool) { - if (!session.destroyed) { - session.destroy(); - } - connectionPool.remove(session); - } - this.sessionCache.delete(key); - } - } - - public setMaxConcurrentStreams(maxConcurrentStreams: number) { - if (this.config.maxConcurrency && this.config.maxConcurrency <= 0) { - throw new RangeError("maxConcurrentStreams must be greater than zero."); - } - this.config.maxConcurrency = maxConcurrentStreams; - } - - public setDisableConcurrentStreams(disableConcurrentStreams: boolean) { - this.config.disableConcurrency = disableConcurrentStreams; - } - - private getUrlString(request: RequestContext): string { - return request.destination.toString(); - } -} diff --git a/packages/node-http-handler/src/node-http2-connection-pool.ts b/packages/node-http-handler/src/node-http2-connection-pool.ts deleted file mode 100644 index ea921b6c56fb..000000000000 --- a/packages/node-http-handler/src/node-http2-connection-pool.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ConnectionPool } from "@aws-sdk/types"; -import { ClientHttp2Session } from "http2"; - -export class NodeHttp2ConnectionPool implements ConnectionPool { - private sessions: ClientHttp2Session[] = []; - - constructor(sessions?: ClientHttp2Session[]) { - this.sessions = sessions ?? []; - } - - public poll(): ClientHttp2Session | void { - if (this.sessions.length > 0) { - return this.sessions.shift(); - } - } - - public offerLast(session: ClientHttp2Session): void { - this.sessions.push(session); - } - - public contains(session: ClientHttp2Session): boolean { - return this.sessions.includes(session); - } - - public remove(session: ClientHttp2Session): void { - this.sessions = this.sessions.filter((s) => s !== session); - } - - public [Symbol.iterator]() { - return this.sessions[Symbol.iterator](); - } - - public destroy(connection: ClientHttp2Session): void { - for (const session of this.sessions) { - if (session === connection) { - if (!session.destroyed) { - session.destroy(); - } - } - } - } -} diff --git a/packages/node-http-handler/src/node-http2-handler.spec.ts b/packages/node-http-handler/src/node-http2-handler.spec.ts deleted file mode 100644 index 574ffaf3dd17..000000000000 --- a/packages/node-http-handler/src/node-http2-handler.spec.ts +++ /dev/null @@ -1,646 +0,0 @@ -import { AbortController } from "@aws-sdk/abort-controller"; -import { HttpRequest, HttpResponse } from "@aws-sdk/protocol-http"; -import { rejects } from "assert"; -import http2, { ClientHttp2Session, ClientHttp2Stream, constants, Http2Server, Http2Stream } from "http2"; -import { Duplex } from "stream"; -import { promisify } from "util"; - -import { NodeHttp2ConnectionPool } from "./node-http2-connection-pool"; -import { NodeHttp2Handler, NodeHttp2HandlerOptions } from "./node-http2-handler"; -import { createMockHttp2Server, createResponseFunction, createResponseFunctionWithDelay } from "./server.mock"; - -describe(NodeHttp2Handler.name, () => { - let nodeH2Handler: NodeHttp2Handler; - - const protocol = "http:"; - const hostname = "localhost"; - const port = 45321; - let mockH2Server: any = undefined; - let mockH2Servers: Record = {}; - - const authority = `${protocol}//${hostname}:${port}/`; - const getMockReqOptions = () => ({ - protocol, - hostname, - port, - method: "GET", - path: "/", - headers: {}, - }); - - const mockResponse = { - statusCode: 200, - headers: {}, - body: "test", - }; - - beforeEach(() => { - mockH2Servers = { - 45321: createMockHttp2Server().listen(port), - 45322: createMockHttp2Server().listen(port + 1), - 45323: createMockHttp2Server().listen(port + 2), - 45324: createMockHttp2Server().listen(port + 3), - }; - mockH2Server = mockH2Servers[port]; - mockH2Server.on("request", createResponseFunction(mockResponse)); - }); - - afterEach(() => { - mockH2Server.removeAllListeners("request"); - jest.clearAllMocks(); - for (const p in mockH2Servers) { - mockH2Servers[p].removeAllListeners("request"); - mockH2Servers[p].close(); - } - }); - - describe.each([ - ["undefined", undefined], - ["empty object", {}], - ["undefined provider", async () => void 0], - ["empty object provider", async () => ({})], - ])("without options in constructor parameter of %s", (_, option) => { - let createdSessions!: ClientHttp2Session[]; - const connectReal = http2.connect; - let connectSpy!: jest.SpiedFunction; - - beforeEach(() => { - createdSessions = []; - connectSpy = jest.spyOn(http2, "connect").mockImplementation((...args) => { - const session = connectReal(...args); - jest.spyOn(session, "ref"); - jest.spyOn(session, "unref"); - jest.spyOn(session, "settings"); - createdSessions.push(session); - return session; - }); - - nodeH2Handler = new NodeHttp2Handler(option); - }); - - const closeConnection = async (response: HttpResponse) => { - const responseBody = response.body as ClientHttp2Stream; - const closePromise = new Promise((resolve) => responseBody.once("close", resolve)); - responseBody.destroy(); - await closePromise; - }; - - // Keeping node alive while request is open. - const expectSessionCreatedAndReferred = (session: ClientHttp2Session, requestCount = 1) => { - expect(session.ref).toHaveBeenCalledTimes(requestCount); - expect(session.unref).toHaveBeenCalledTimes(1); - }; - - // No longer keeping node alive - const expectSessionCreatedAndUnreffed = (session: ClientHttp2Session, requestCount = 1) => { - expect(session.ref).toHaveBeenCalledTimes(requestCount); - expect(session.unref).toHaveBeenCalledTimes(requestCount + 1); - }; - - afterEach(() => { - nodeH2Handler.destroy(); - }); - - it("has metadata", () => { - expect(nodeH2Handler.metadata.handlerProtocol).toContain("h2"); - }); - - describe("number calls to http2.connect", () => { - it("is zero on initialization", () => { - expect(connectSpy).not.toHaveBeenCalled(); - }); - - it("is one when request is made", async () => { - // Make single request. - const { response } = await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - - expect(connectSpy).toHaveBeenCalledTimes(1); - expect(connectSpy).toHaveBeenCalledWith(authority); - - expectSessionCreatedAndReferred(createdSessions[0]); - await closeConnection(response); - expectSessionCreatedAndUnreffed(createdSessions[0]); - }); - - it("is one if multiple requests are made on same URL", async () => { - const connectSpy = jest.spyOn(http2, "connect"); - - // Make two requests. - const { response: response1 } = await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - const { response: response2 } = await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - - expect(connectSpy).toHaveBeenCalledTimes(1); - expect(connectSpy).toHaveBeenCalledWith(authority); - - expectSessionCreatedAndReferred(createdSessions[0], 2); - await closeConnection(response1); - await closeConnection(response2); - expectSessionCreatedAndUnreffed(createdSessions[0], 2); - }); - - it("is many if requests are made on different URLs", async () => { - const connectSpy = jest.spyOn(http2, "connect"); - - // Make first request on default URL. - const { response: response1 } = await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - - const port2 = port + 1; - const mockH2Server2 = mockH2Servers[port2]; - mockH2Server2.on("request", createResponseFunction(mockResponse)); - - // Make second request on URL with port2. - const { response: response2 } = await nodeH2Handler.handle( - new HttpRequest({ ...getMockReqOptions(), port: port2 }), - {} - ); - - const authorityPrefix = `${protocol}//${hostname}`; - expect(connectSpy).toHaveBeenCalledTimes(2); - expect(connectSpy).toHaveBeenNthCalledWith(1, `${authorityPrefix}:${port}/`); - expect(connectSpy).toHaveBeenNthCalledWith(2, `${authorityPrefix}:${port2}/`); - mockH2Server2.close(); - - expectSessionCreatedAndReferred(createdSessions[0]); - expectSessionCreatedAndReferred(createdSessions[1]); - await closeConnection(response1); - await closeConnection(response2); - expectSessionCreatedAndUnreffed(createdSessions[0]); - expectSessionCreatedAndUnreffed(createdSessions[1]); - }); - }); - - describe("errors", () => { - const UNEXPECTEDLY_CLOSED_REGEX = /closed|destroy|cancel|did not get a response/i; - it("handles goaway frames", async () => { - const port3 = port + 2; - const mockH2Server3 = mockH2Servers[port3]; - let establishedConnections = 0; - let numRequests = 0; - let shouldSendGoAway = true; - - mockH2Server3.on("stream", (request: Http2Stream) => { - // transmit goaway frame without shutting down the connection - // to simulate an unlikely error mode. - numRequests += 1; - if (shouldSendGoAway) { - request.session.goaway(constants.NGHTTP2_PROTOCOL_ERROR); - } - }); - mockH2Server3.on("connection", () => { - establishedConnections += 1; - }); - const req = new HttpRequest({ ...getMockReqOptions(), port: port3 }); - expect(establishedConnections).toBe(0); - expect(numRequests).toBe(0); - await rejects( - nodeH2Handler.handle(req, {}), - UNEXPECTEDLY_CLOSED_REGEX, - "should be rejected promptly due to goaway frame" - ); - expect(establishedConnections).toBe(1); - expect(numRequests).toBe(1); - await rejects( - nodeH2Handler.handle(req, {}), - UNEXPECTEDLY_CLOSED_REGEX, - "should be rejected promptly due to goaway frame" - ); - expect(establishedConnections).toBe(2); - expect(numRequests).toBe(2); - await rejects( - nodeH2Handler.handle(req, {}), - UNEXPECTEDLY_CLOSED_REGEX, - "should be rejected promptly due to goaway frame" - ); - expect(establishedConnections).toBe(3); - expect(numRequests).toBe(3); - - // Not keeping node alive - expect(createdSessions).toHaveLength(3); - expectSessionCreatedAndUnreffed(createdSessions[0]); - expectSessionCreatedAndUnreffed(createdSessions[1]); - expectSessionCreatedAndUnreffed(createdSessions[2]); - - // should be able to recover from goaway after reconnecting to a server - // that doesn't send goaway, and reuse the TCP connection (Http2Session) - shouldSendGoAway = false; - mockH2Server3.on("request", createResponseFunction(mockResponse)); - const result = await nodeH2Handler.handle(req, {}); - const resultReader = result.response.body; - - // Keeping node alive - expect(createdSessions).toHaveLength(4); - expectSessionCreatedAndReferred(createdSessions[3]); - - // ...and validate that the mocked response is received - const responseBody = await new Promise((resolve) => { - const buffers: any[] = []; - resultReader.on("data", (chunk) => buffers.push(chunk)); - resultReader.on("close", () => { - resolve(Buffer.concat(buffers).toString("utf8")); - }); - }); - expect(responseBody).toBe("test"); - expect(establishedConnections).toBe(4); - expect(numRequests).toBe(4); - mockH2Server3.close(); - - // Not keeping node alive - expect(createdSessions).toHaveLength(4); - expectSessionCreatedAndUnreffed(createdSessions[3]); - }); - - it.each([ - ["destroy", port + 2], - ["close", port + 3], - ])("handles servers calling connections %s", async (func, port) => { - const mockH2Server4 = mockH2Servers[port]; - let establishedConnections = 0; - let numRequests = 0; - - mockH2Server4.on("stream", (request: Http2Stream) => { - numRequests += 1; - request.session[func](); - }); - mockH2Server4.on("connection", () => { - establishedConnections += 1; - }); - const req = new HttpRequest({ ...getMockReqOptions(), port }); - expect(establishedConnections).toBe(0); - expect(numRequests).toBe(0); - await rejects( - nodeH2Handler.handle(req, {}), - UNEXPECTEDLY_CLOSED_REGEX, - "should be rejected promptly due to goaway frame or destroyed connection" - ); - expect(establishedConnections).toBe(1); - expect(numRequests).toBe(1); - await rejects( - nodeH2Handler.handle(req, {}), - UNEXPECTEDLY_CLOSED_REGEX, - "should be rejected promptly due to goaway frame or destroyed connection" - ); - expect(establishedConnections).toBe(2); - expect(numRequests).toBe(2); - await rejects( - nodeH2Handler.handle(req, {}), - UNEXPECTEDLY_CLOSED_REGEX, - "should be rejected promptly due to goaway frame or destroyed connection" - ); - expect(establishedConnections).toBe(3); - expect(numRequests).toBe(3); - mockH2Server4.close(); - - // Not keeping node alive - expect(createdSessions).toHaveLength(3); - expectSessionCreatedAndUnreffed(createdSessions[0]); - expectSessionCreatedAndUnreffed(createdSessions[1]); - expectSessionCreatedAndUnreffed(createdSessions[2]); - }); - }); - - describe("destroy", () => { - it("destroys session and clears sessionCache", async () => { - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - - // @ts-ignore: access private property - const session: ClientHttp2Session = nodeH2Handler.connectionManager.sessionCache.get(authority).sessions[0]; - - // @ts-ignore: access private property - expect(nodeH2Handler.connectionManager.sessionCache.size).toBe(1); - expect(session.destroyed).toBe(false); - nodeH2Handler.destroy(); - // @ts-ignore: access private property - expect(nodeH2Handler.connectionManager.sessionCache.size).toBe(0); - expect(session.destroyed).toBe(true); - }); - }); - - describe("abortSignal", () => { - it("will not create session if request already aborted", async () => { - // @ts-ignore: access private property - expect(nodeH2Handler.connectionManager.sessionCache.size).toBe(0); - await expect( - nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), { - abortSignal: { - aborted: true, - onabort: null, - }, - }) - ).rejects.toHaveProperty("name", "AbortError"); - // @ts-ignore: access private property - expect(nodeH2Handler.connectionManager.sessionCache.size).toBe(0); - }); - - it("will not create request on session if request already aborted", async () => { - // Create a session by sending a request. - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - - // @ts-ignore: access private property - const session: ClientHttp2Session = nodeH2Handler.connectionManager.sessionCache.get(authority).sessions[0]; - const requestSpy = jest.spyOn(session, "request"); - - await expect( - nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), { - abortSignal: { - aborted: true, - onabort: null, - }, - }) - ).rejects.toHaveProperty("name", "AbortError"); - expect(requestSpy.mock.calls.length).toBe(0); - }); - - it("will close request on session when aborted", async () => { - const abortController = new AbortController(); - mockH2Server.removeAllListeners("request"); - mockH2Server.on("request", (request: any, response: any) => { - abortController.abort(); - return createResponseFunction(mockResponse); - }); - - await expect( - nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), { - abortSignal: abortController.signal, - }) - ).rejects.toHaveProperty("name", "AbortError"); - }); - }); - }); - - describe("requestTimeout", () => { - const requestTimeout = 200; - - describe("does not throw error when request not timed out", () => { - it.each([ - ["static object", { requestTimeout }], - ["object provider", async () => ({ requestTimeout })], - ])("disableConcurrentStreams: false (default) in constructor parameter of %s", async (_, options) => { - mockH2Server.removeAllListeners("request"); - mockH2Server.on("request", createResponseFunctionWithDelay(mockResponse, requestTimeout - 100)); - - nodeH2Handler = new NodeHttp2Handler(options); - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - }); - - it.each([ - ["static object", { requestTimeout, disableConcurrentStreams: true }], - ["object provider", async () => ({ requestTimeout, disableConcurrentStreams: true })], - ])("disableConcurrentStreams: true in constructor parameter of %s", async (_, options) => { - mockH2Server.removeAllListeners("request"); - mockH2Server.on("request", createResponseFunctionWithDelay(mockResponse, requestTimeout - 100)); - - nodeH2Handler = new NodeHttp2Handler(options); - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - }); - }); - - describe("throws timeoutError on requestTimeout", () => { - it.each([ - ["static object", { requestTimeout }], - ["object provider", async () => ({ requestTimeout })], - ])("disableConcurrentStreams: false (default) in constructor parameter of %s", async (_, options) => { - mockH2Server.removeAllListeners("request"); - mockH2Server.on("request", createResponseFunctionWithDelay(mockResponse, requestTimeout + 100)); - - nodeH2Handler = new NodeHttp2Handler(options); - await rejects(nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}), { - name: "TimeoutError", - message: `Stream timed out because of no activity for ${requestTimeout} ms`, - }); - }); - - it.each([ - ["object provider", async () => ({ requestTimeout })], - ["static object", { requestTimeout }], - ])("disableConcurrentStreams: true in constructor parameter of %s", async () => { - mockH2Server.removeAllListeners("request"); - mockH2Server.on("request", createResponseFunctionWithDelay(mockResponse, requestTimeout + 100)); - - nodeH2Handler = new NodeHttp2Handler({ requestTimeout, disableConcurrentStreams: true }); - await rejects(nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}), { - name: "TimeoutError", - message: `Stream timed out because of no activity for ${requestTimeout} ms`, - }); - }); - }); - }); - - describe("sessionTimeout", () => { - const sessionTimeout = 200; - - describe("destroys sessions on sessionTimeout", () => { - it.each([ - ["object provider", async () => ({ sessionTimeout })], - ["static object", { sessionTimeout }], - ])("disableConcurrentStreams: false (default) in constructor parameter of %s", async (_, options) => { - nodeH2Handler = new NodeHttp2Handler(options); - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), { requestTimeout: sessionTimeout }); - - // @ts-ignore: access private property - const session: ClientHttp2Session = nodeH2Handler.connectionManager.sessionCache.get(authority).sessions[0]; - expect(session.destroyed).toBe(false); - // @ts-ignore: access private property - expect(nodeH2Handler.connectionManager.sessionCache.get(authority).sessions.length).toStrictEqual(1); - await promisify(setTimeout)(sessionTimeout + 100); - expect(session.destroyed).toBe(true); - // @ts-ignore: access private property - expect(nodeH2Handler.connectionManager.sessionCache.get(authority).sessions.length).toStrictEqual(0); - }); - - it.each([ - ["object provider", async () => ({ sessionTimeout, disableConcurrentStreams: true })], - ["static object", { sessionTimeout, disableConcurrentStreams: true }], - ])("disableConcurrentStreams: true in constructor parameter of %s", async (_, options) => { - let session; - - nodeH2Handler = new NodeHttp2Handler(options); - - mockH2Server.removeAllListeners("request"); - mockH2Server.on("request", (request: any, response: any) => { - // @ts-ignore: access private property - session = nodeH2Handler.connectionManager.sessionCache.get(authority).sessions[0]; - createResponseFunction(mockResponse)(request, response); - }); - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - - expect(session.destroyed).toBe(false); - await promisify(setTimeout)(sessionTimeout + 100); - expect(session.destroyed).toBe(true); - }); - }); - }); - - describe("maxConcurrency", () => { - it.each([ - ["static object", {}], - ["static object", { maxConcurrentStreams: 0 }], - ["static object", { maxConcurrentStreams: 1 }], - ["static object", { maxConcurrentStreams: 2 }], - ["static object", { maxConcurrentStreams: 3 }], - ])("verify session settings' maxConcurrentStreams", async (_, options: NodeHttp2HandlerOptions) => { - nodeH2Handler = new NodeHttp2Handler(options); - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - - // @ts-ignore: access private property - const session = nodeH2Handler.connectionManager.sessionCache.get(authority).sessions[0]; - - if (options.maxConcurrentStreams) { - expect(session.localSettings.maxConcurrentStreams).toBe(options.maxConcurrentStreams); - expect(session.settings).toHaveBeenCalled(); - } else { - expect(session.localSettings.maxConcurrentStreams).toBe(4294967295); - } - }); - - it("verify error thrown when maxConcurrentStreams is negative", async () => { - let error: Error | undefined = undefined; - try { - nodeH2Handler = new NodeHttp2Handler({ maxConcurrentStreams: -1 }); - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - } catch (e) { - error = e; - } - - expect(error).toBeDefined(); - expect(error!.message).toEqual('Invalid value for setting "maxConcurrentStreams": -1'); - }); - }); - - it("will throw reasonable error when connection aborted abnormally", async () => { - nodeH2Handler = new NodeHttp2Handler(); - // Create a session by sending a request. - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - // @ts-ignore: access private property - const session: ClientHttp2Session = nodeH2Handler.connectionManager.sessionCache.get(authority).sessions[0]; - const fakeStream = new Duplex() as ClientHttp2Stream; - const fakeRstCode = 1; - // @ts-ignore: fake result code - fakeStream.rstCode = fakeRstCode; - jest.spyOn(session, "request").mockImplementation(() => fakeStream); - // @ts-ignore: access private property - nodeH2Handler.connectionManager.sessionCache.set(authority, new NodeHttp2ConnectionPool([session])); - // Delay response so that onabort is called earlier - setTimeout(() => { - fakeStream.emit("aborted"); - }, 0); - - await expect(nodeH2Handler.handle(new HttpRequest({ ...getMockReqOptions() }), {})).rejects.toHaveProperty( - "message", - `HTTP/2 stream is abnormally aborted in mid-communication with result code ${fakeRstCode}.` - ); - }); - - it("will throw reasonable error when frameError is thrown", async () => { - nodeH2Handler = new NodeHttp2Handler(); - // Create a session by sending a request. - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - // @ts-ignore: access private property - const session: ClientHttp2Session = nodeH2Handler.connectionManager.sessionCache.get(authority).sessions[0]; - const fakeStream = new Duplex() as ClientHttp2Stream; - jest.spyOn(session, "request").mockImplementation(() => fakeStream); - // @ts-ignore: access private property - nodeH2Handler.connectionManager.sessionCache.set(authority, new NodeHttp2ConnectionPool([session])); - // Delay response so that onabort is called earlier - setTimeout(() => { - fakeStream.emit("frameError", "TYPE", "CODE", "ID"); - }, 0); - - await expect(nodeH2Handler.handle(new HttpRequest({ ...getMockReqOptions() }), {})).rejects.toHaveProperty( - "message", - `Frame type id TYPE in stream id ID has failed with code CODE.` - ); - }); - - describe.each([ - ["object provider", async () => ({ disableConcurrentStreams: true })], - ["static object", { disableConcurrentStreams: true }], - ])("disableConcurrentStreams in constructor parameter of %s", (_, options) => { - beforeEach(() => { - nodeH2Handler = new NodeHttp2Handler(options); - }); - - describe("number calls to http2.connect", () => { - it("is zero on initialization", () => { - const connectSpy = jest.spyOn(http2, "connect"); - expect(connectSpy).not.toHaveBeenCalled(); - }); - - it("is one when request is made", async () => { - const connectSpy = jest.spyOn(http2, "connect"); - - // Make single request. - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - expect(connectSpy).toHaveBeenCalledTimes(1); - expect(connectSpy).toHaveBeenCalledWith(authority); - }); - - it("is many if multiple requests are made on same URL", async () => { - const connectSpy = jest.spyOn(http2, "connect"); - - // Make two requests. - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - - expect(connectSpy).toHaveBeenCalledTimes(2); - expect(connectSpy).toHaveBeenNthCalledWith(1, authority); - expect(connectSpy).toHaveBeenNthCalledWith(2, authority); - }); - - it("is many if requests are made on different URLs", async () => { - const connectSpy = jest.spyOn(http2, "connect"); - - // Make first request on default URL. - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - - const port2 = port + 1; - const mockH2Server2 = mockH2Servers[port2]; - mockH2Server2.on("request", createResponseFunction(mockResponse)); - - // Make second request on URL with port2. - await nodeH2Handler.handle(new HttpRequest({ ...getMockReqOptions(), port: port2 }), {}); - - const authorityPrefix = `${protocol}//${hostname}`; - expect(connectSpy).toHaveBeenCalledTimes(2); - expect(connectSpy).toHaveBeenNthCalledWith(1, `${authorityPrefix}:${port}/`); - expect(connectSpy).toHaveBeenNthCalledWith(2, `${authorityPrefix}:${port2}/`); - mockH2Server2.close(); - }); - }); - - describe("destroy", () => { - it("destroys session and empties sessionCache", async () => { - await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {}); - - // @ts-ignore: access private property - const session: ClientHttp2Session = nodeH2Handler.connectionManager.sessionCache.get(authority).sessions[0]; - - // @ts-ignore: access private property - expect(nodeH2Handler.connectionManager.sessionCache.size).toBe(1); - expect(session.destroyed).toBe(false); - - nodeH2Handler.destroy(); - // @ts-ignore: access private property - expect(nodeH2Handler.connectionManager.sessionCache.size).toBe(0); - expect(session.destroyed).toBe(true); - }); - }); - }); - - it("sends the request to the correct url", async () => { - const server = createMockHttp2Server(); - server.on("request", (request, response) => { - expect(request.url).toBe("http://foo:bar@localhost/foo/bar?foo=bar#foo"); - response.statusCode = 200; - }); - const handler = new NodeHttp2Handler({}); - await handler.handle({ - ...getMockReqOptions(), - username: "foo", - password: "bar", - path: "/foo/bar", - query: { foo: "bar" }, - fragment: "foo", - } as any); - handler.destroy(); - }); -}); diff --git a/packages/node-http-handler/src/node-http2-handler.ts b/packages/node-http-handler/src/node-http2-handler.ts deleted file mode 100644 index 5f256f84c090..000000000000 --- a/packages/node-http-handler/src/node-http2-handler.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { HttpHandler, HttpRequest, HttpResponse } from "@aws-sdk/protocol-http"; -import { buildQueryString } from "@aws-sdk/querystring-builder"; -import { ConnectConfiguration, HttpHandlerOptions, Provider, RequestContext } from "@aws-sdk/types"; -import { ClientHttp2Session, constants } from "http2"; - -import { getTransformedHeaders } from "./get-transformed-headers"; -import { NodeHttp2ConnectionManager } from "./node-http2-connection-manager"; -import { writeRequestBody } from "./write-request-body"; - -/** - * Represents the http2 options that can be passed to a node http2 client. - */ -export interface NodeHttp2HandlerOptions { - /** - * The maximum time in milliseconds that a stream may remain idle before it - * is closed. - */ - requestTimeout?: number; - - /** - * The maximum time in milliseconds that a session or socket may remain idle - * before it is closed. - * https://nodejs.org/docs/latest-v12.x/api/http2.html#http2_http2session_and_sockets - */ - sessionTimeout?: number; - - /** - * Disables processing concurrent streams on a ClientHttp2Session instance. When set - * to true, a new session instance is created for each request to a URL. - * **Default:** false. - * https://nodejs.org/api/http2.html#http2_class_clienthttp2session - */ - disableConcurrentStreams?: boolean; - - /** - * Maximum number of concurrent Http2Stream instances per ClientHttp2Session. Each session - * may have up to 2^31-1 Http2Stream instances over its lifetime. - * This value must be greater than or equal to 0. - * https://nodejs.org/api/http2.html#class-http2stream - */ - maxConcurrentStreams?: number; -} - -export class NodeHttp2Handler implements HttpHandler { - private config?: NodeHttp2HandlerOptions; - private readonly configProvider: Promise; - - public readonly metadata = { handlerProtocol: "h2" }; - - private readonly connectionManager: NodeHttp2ConnectionManager = new NodeHttp2ConnectionManager({}); - - constructor(options?: NodeHttp2HandlerOptions | Provider) { - this.configProvider = new Promise((resolve, reject) => { - if (typeof options === "function") { - options() - .then((opts) => { - resolve(opts || {}); - }) - .catch(reject); - } else { - resolve(options || {}); - } - }); - } - - destroy(): void { - this.connectionManager.destroy(); - } - - async handle(request: HttpRequest, { abortSignal }: HttpHandlerOptions = {}): Promise<{ response: HttpResponse }> { - if (!this.config) { - this.config = await this.configProvider; - this.connectionManager.setDisableConcurrentStreams(this.config.disableConcurrentStreams || false); - if (this.config.maxConcurrentStreams) { - this.connectionManager.setMaxConcurrentStreams(this.config.maxConcurrentStreams); - } - } - const { requestTimeout, disableConcurrentStreams } = this.config; - return new Promise((_resolve, _reject) => { - // It's redundant to track fulfilled because promises use the first resolution/rejection - // but avoids generating unnecessary stack traces in the "close" event handler. - let fulfilled = false; - - let writeRequestBodyPromise: Promise | undefined = undefined; - const resolve = async (arg: { response: HttpResponse }) => { - await writeRequestBodyPromise; - _resolve(arg); - }; - const reject = async (arg: unknown) => { - await writeRequestBodyPromise; - _reject(arg); - }; - - // if the request was already aborted, prevent doing extra work - if (abortSignal?.aborted) { - fulfilled = true; - const abortError = new Error("Request aborted"); - abortError.name = "AbortError"; - reject(abortError); - return; - } - - const { hostname, method, port, protocol, query } = request; - let auth = ""; - if (request.username != null || request.password != null) { - const username = request.username ?? ""; - const password = request.password ?? ""; - auth = `${username}:${password}@`; - } - const authority = `${protocol}//${auth}${hostname}${port ? `:${port}` : ""}`; - const requestContext = { destination: new URL(authority) } as RequestContext; - const session = this.connectionManager.lease(requestContext, { - requestTimeout: this.config?.sessionTimeout, - disableConcurrentStreams: disableConcurrentStreams || false, - } as ConnectConfiguration); - - const rejectWithDestroy = (err: Error) => { - if (disableConcurrentStreams) { - this.destroySession(session); - } - fulfilled = true; - reject(err); - }; - - const queryString = buildQueryString(query || {}); - let path = request.path; - if (queryString) { - path += `?${queryString}`; - } - if (request.fragment) { - path += `#${request.fragment}`; - } - // create the http2 request - const req = session.request({ - ...request.headers, - [constants.HTTP2_HEADER_PATH]: path, - [constants.HTTP2_HEADER_METHOD]: method, - }); - - // Keep node alive while request is in progress. Matched with unref() in close event. - session.ref(); - - req.on("response", (headers) => { - const httpResponse = new HttpResponse({ - statusCode: headers[":status"] || -1, - headers: getTransformedHeaders(headers), - body: req, - }); - fulfilled = true; - resolve({ response: httpResponse }); - if (disableConcurrentStreams) { - // Gracefully closes the Http2Session, allowing any existing streams to complete - // on their own and preventing new Http2Stream instances from being created. - session.close(); - this.connectionManager.deleteSession(authority, session); - } - }); - - if (requestTimeout) { - req.setTimeout(requestTimeout, () => { - req.close(); - const timeoutError = new Error(`Stream timed out because of no activity for ${requestTimeout} ms`); - timeoutError.name = "TimeoutError"; - rejectWithDestroy(timeoutError); - }); - } - - if (abortSignal) { - abortSignal.onabort = () => { - req.close(); - const abortError = new Error("Request aborted"); - abortError.name = "AbortError"; - rejectWithDestroy(abortError); - }; - } - - // Set up handlers for errors - req.on("frameError", (type: number, code: number, id: number) => { - rejectWithDestroy(new Error(`Frame type id ${type} in stream id ${id} has failed with code ${code}.`)); - }); - req.on("error", rejectWithDestroy); - req.on("aborted", () => { - rejectWithDestroy( - new Error(`HTTP/2 stream is abnormally aborted in mid-communication with result code ${req.rstCode}.`) - ); - }); - - // The HTTP/2 error code used when closing the stream can be retrieved using the - // http2stream.rstCode property. If the code is any value other than NGHTTP2_NO_ERROR (0), - // an 'error' event will have also been emitted. - req.on("close", () => { - session.unref(); - if (disableConcurrentStreams) { - session.destroy(); - } - if (!fulfilled) { - rejectWithDestroy(new Error("Unexpected error: http2 request did not get a response")); - } - }); - - writeRequestBodyPromise = writeRequestBody(req, request, requestTimeout); - }); - } - - /** - * Destroys a session. - * @param session The session to destroy. - */ - private destroySession(session: ClientHttp2Session): void { - if (!session.destroyed) { - session.destroy(); - } - } -} diff --git a/packages/node-http-handler/src/readable.mock.ts b/packages/node-http-handler/src/readable.mock.ts deleted file mode 100644 index c8605e50cdd3..000000000000 --- a/packages/node-http-handler/src/readable.mock.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Readable, ReadableOptions } from "stream"; - -export interface ReadFromBuffersOptions extends ReadableOptions { - buffers: Buffer[]; - errorAfter?: number; -} - -export class ReadFromBuffers extends Readable { - private buffersToRead: Buffer[]; - private numBuffersRead = 0; - - private errorAfter: number; - constructor(options: ReadFromBuffersOptions) { - super(options); - this.buffersToRead = options.buffers; - this.errorAfter = typeof options.errorAfter === "number" ? options.errorAfter : -1; - } - - _read() { - if (this.errorAfter !== -1 && this.errorAfter === this.numBuffersRead) { - this.emit("error", new Error("Mock Error")); - return; - } - if (this.numBuffersRead >= this.buffersToRead.length) { - return this.push(null); - } - return this.push(this.buffersToRead[this.numBuffersRead++]); - } -} diff --git a/packages/node-http-handler/src/server.mock.ts b/packages/node-http-handler/src/server.mock.ts deleted file mode 100644 index 992a7e0a14a5..000000000000 --- a/packages/node-http-handler/src/server.mock.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { HeaderBag, HttpResponse } from "@aws-sdk/types"; -import { readFileSync } from "fs"; -import { createServer as createHttpServer, IncomingMessage, Server as HttpServer, ServerResponse } from "http"; -import { createServer as createHttp2Server, Http2Server } from "http2"; -import { createServer as createHttpsServer, Server as HttpsServer } from "https"; -import { join } from "path"; -import { Readable } from "stream"; - -const fixturesDir = join(__dirname, "..", "fixtures"); - -const setResponseHeaders = (response: ServerResponse, headers: HeaderBag) => { - for (const [key, value] of Object.entries(headers)) { - response.setHeader(key, value); - } -}; - -const setResponseBody = (response: ServerResponse, body: Readable | string) => { - if (body instanceof Readable) { - body.pipe(response); - } else { - response.end(body); - } -}; - -export const createResponseFunction = - (httpResp: HttpResponse) => (request: IncomingMessage, response: ServerResponse) => { - response.statusCode = httpResp.statusCode; - setResponseHeaders(response, httpResp.headers); - setResponseBody(response, httpResp.body); - }; - -export const createResponseFunctionWithDelay = - (httpResp: HttpResponse, delay: number) => (request: IncomingMessage, response: ServerResponse) => { - response.statusCode = httpResp.statusCode; - setResponseHeaders(response, httpResp.headers); - setTimeout(() => setResponseBody(response, httpResp.body), delay); - }; - -export const createContinueResponseFunction = - (httpResp: HttpResponse) => (request: IncomingMessage, response: ServerResponse) => { - response.writeContinue(); - setTimeout(() => { - createResponseFunction(httpResp)(request, response); - }, 100); - }; - -export const createMockHttpsServer = (): HttpsServer => { - const server = createHttpsServer({ - key: readFileSync(join(fixturesDir, "test-server-key.pem")), - cert: readFileSync(join(fixturesDir, "test-server-cert.pem")), - }); - return server; -}; - -export const createMockHttpServer = (): HttpServer => { - const server = createHttpServer(); - return server; -}; - -export const createMockHttp2Server = (): Http2Server => { - const server = createHttp2Server(); - return server; -}; diff --git a/packages/node-http-handler/src/set-connection-timeout.spec.ts b/packages/node-http-handler/src/set-connection-timeout.spec.ts deleted file mode 100644 index 0f82815f13a0..000000000000 --- a/packages/node-http-handler/src/set-connection-timeout.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { setConnectionTimeout } from "./set-connection-timeout"; - -describe("setConnectionTimeout", () => { - const reject = jest.fn(); - const clientRequest: any = { - on: jest.fn(), - destroy: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it("will not attach listeners if timeout is 0", () => { - setConnectionTimeout(clientRequest, reject, 0); - expect(clientRequest.on).not.toHaveBeenCalled(); - }); - - it("will not attach listeners if timeout is not provided", () => { - setConnectionTimeout(clientRequest, reject); - expect(clientRequest.on).not.toHaveBeenCalled(); - }); - - describe("when timeout is provided", () => { - const timeoutInMs = 100; - const mockSocket = { - connecting: true, - on: jest.fn(), - }; - - beforeEach(() => { - jest.useFakeTimers({ legacyFakeTimers: true }); - setConnectionTimeout(clientRequest, reject, timeoutInMs); - }); - - afterEach(() => { - jest.advanceTimersByTime(10000); - jest.useRealTimers(); - }); - - it("attaches listener", () => { - expect(clientRequest.on).toHaveBeenCalledTimes(1); - expect(clientRequest.on).toHaveBeenCalledWith("socket", expect.any(Function)); - }); - - it("doesn't set timeout if socket is already connected", () => { - clientRequest.on.mock.calls[0][1]({ - ...mockSocket, - connecting: false, - }); - expect(mockSocket.on).not.toHaveBeenCalled(); - expect(setTimeout).toHaveBeenCalled(); - expect(reject).not.toHaveBeenCalled(); - }); - - it("rejects and aborts request if socket isn't connected by timeout", () => { - clientRequest.on.mock.calls[0][1](mockSocket); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), timeoutInMs); - expect(mockSocket.on).toHaveBeenCalledTimes(1); - expect(mockSocket.on).toHaveBeenCalledWith("connect", expect.any(Function)); - - expect(clientRequest.destroy).not.toHaveBeenCalled(); - expect(reject).not.toHaveBeenCalled(); - - // Fast-forward until timer has been executed. - jest.advanceTimersByTime(timeoutInMs); - expect(clientRequest.destroy).toHaveBeenCalledTimes(1); - expect(reject).toHaveBeenCalledTimes(1); - expect(reject).toHaveBeenCalledWith( - Object.assign(new Error(`Socket timed out without establishing a connection within ${timeoutInMs} ms`), { - name: "TimeoutError", - }) - ); - }); - - it("clears timeout if socket gets connected", () => { - clientRequest.on.mock.calls[0][1](mockSocket); - - expect(clientRequest.destroy).not.toHaveBeenCalled(); - expect(reject).not.toHaveBeenCalled(); - expect(clearTimeout).not.toHaveBeenCalled(); - - // Fast-forward for half the amount of time and call connect callback to clear timer. - jest.advanceTimersByTime(timeoutInMs / 2); - mockSocket.on.mock.calls[0][1](); - - expect(clearTimeout).toHaveBeenCalled(); - - // Fast-forward until timer has been executed. - jest.runAllTimers(); - expect(clientRequest.destroy).not.toHaveBeenCalled(); - expect(reject).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/packages/node-http-handler/src/set-connection-timeout.ts b/packages/node-http-handler/src/set-connection-timeout.ts deleted file mode 100644 index 3acbeec7637a..000000000000 --- a/packages/node-http-handler/src/set-connection-timeout.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ClientRequest } from "http"; -import { Socket } from "net"; - -export const setConnectionTimeout = (request: ClientRequest, reject: (err: Error) => void, timeoutInMs = 0) => { - if (!timeoutInMs) { - return; - } - - // Throw a connecting timeout error unless a connection is made within time. - const timeoutId = setTimeout(() => { - request.destroy(); - reject( - Object.assign(new Error(`Socket timed out without establishing a connection within ${timeoutInMs} ms`), { - name: "TimeoutError", - }) - ); - }, timeoutInMs); - - request.on("socket", (socket: Socket) => { - if (socket.connecting) { - socket.on("connect", () => { - clearTimeout(timeoutId); - }); - } else { - clearTimeout(timeoutId); - } - }); -}; diff --git a/packages/node-http-handler/src/set-socket-keep-alive.spec.ts b/packages/node-http-handler/src/set-socket-keep-alive.spec.ts deleted file mode 100644 index 27502d51ed0e..000000000000 --- a/packages/node-http-handler/src/set-socket-keep-alive.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { EventEmitter } from "events"; -import { ClientRequest } from "http"; -import { Socket } from "net"; - -import { setSocketKeepAlive } from "./set-socket-keep-alive"; - -describe("setSocketKeepAlive", () => { - let request: ClientRequest; - let socket: Socket; - - beforeEach(() => { - request = new EventEmitter() as ClientRequest; - socket = new Socket(); - }); - - it("should set keepAlive to true", () => { - setSocketKeepAlive(request, { keepAlive: true }); - - const setKeepAliveSpy = jest.spyOn(socket, "setKeepAlive"); - request.emit("socket", socket); - - expect(setKeepAliveSpy).toHaveBeenCalled(); - expect(setKeepAliveSpy).toHaveBeenCalledWith(true, 0); - }); - - it("should set keepAlive to true with custom initialDelay", () => { - const initialDelay = 5 * 1000; - setSocketKeepAlive(request, { keepAlive: true, keepAliveMsecs: initialDelay }); - - const setKeepAliveSpy = jest.spyOn(socket, "setKeepAlive"); - request.emit("socket", socket); - - expect(setKeepAliveSpy).toHaveBeenCalled(); - expect(setKeepAliveSpy).toHaveBeenCalledWith(true, initialDelay); - }); - - it("should not set keepAlive at all when keepAlive is false", () => { - setSocketKeepAlive(request, { keepAlive: false }); - - const setKeepAliveSpy = jest.spyOn(socket, "setKeepAlive"); - request.emit("socket", socket); - - expect(setKeepAliveSpy).not.toHaveBeenCalled(); - }); -}); - diff --git a/packages/node-http-handler/src/set-socket-keep-alive.ts b/packages/node-http-handler/src/set-socket-keep-alive.ts deleted file mode 100644 index 4ae9c079efe1..000000000000 --- a/packages/node-http-handler/src/set-socket-keep-alive.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ClientRequest } from "http"; - -export interface SocketKeepAliveOptions { - keepAlive: boolean; - keepAliveMsecs?: number; -} - -export const setSocketKeepAlive = (request: ClientRequest, { keepAlive, keepAliveMsecs }: SocketKeepAliveOptions) => { - if (keepAlive !== true) { - return; - } - - request.on("socket", (socket) => { - socket.setKeepAlive(keepAlive, keepAliveMsecs || 0); - }); -}; diff --git a/packages/node-http-handler/src/set-socket-timeout.spec.ts b/packages/node-http-handler/src/set-socket-timeout.spec.ts deleted file mode 100644 index 8b44679af557..000000000000 --- a/packages/node-http-handler/src/set-socket-timeout.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { setSocketTimeout } from "./set-socket-timeout"; - -describe("setSocketTimeout", () => { - const clientRequest: any = { - destroy: jest.fn(), - setTimeout: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it(`sets the request's timeout if provided`, () => { - setSocketTimeout(clientRequest, jest.fn(), 100); - - expect(clientRequest.setTimeout).toHaveBeenCalledTimes(1); - expect(clientRequest.setTimeout).toHaveBeenLastCalledWith(100, expect.any(Function)); - }); - - it(`sets the request's timeout to 0 if not provided`, () => { - setSocketTimeout(clientRequest, jest.fn()); - - expect(clientRequest.setTimeout).toHaveBeenCalledTimes(1); - expect(clientRequest.setTimeout).toHaveBeenLastCalledWith(0, expect.any(Function)); - }); - - it(`destroys the request on timeout`, () => { - setSocketTimeout(clientRequest, jest.fn()); - expect(clientRequest.destroy).not.toHaveBeenCalled(); - - // call setTimeout callback - clientRequest.setTimeout.mock.calls[0][1](); - expect(clientRequest.destroy).toHaveBeenCalledTimes(1); - }); - - it(`rejects on timeout with a TimeoutError`, () => { - const reject = jest.fn(); - const timeoutInMs = 100; - - setSocketTimeout(clientRequest, reject, timeoutInMs); - expect(reject).not.toHaveBeenCalled(); - - // call setTimeout callback - clientRequest.setTimeout.mock.calls[0][1](); - expect(reject).toHaveBeenCalledTimes(1); - expect(reject).toHaveBeenCalledWith( - Object.assign(new Error(`Connection timed out after ${timeoutInMs} ms`), { name: "TimeoutError" }) - ); - }); -}); diff --git a/packages/node-http-handler/src/set-socket-timeout.ts b/packages/node-http-handler/src/set-socket-timeout.ts deleted file mode 100644 index 35a7b3c396ca..000000000000 --- a/packages/node-http-handler/src/set-socket-timeout.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ClientRequest } from "http"; - -export const setSocketTimeout = (request: ClientRequest, reject: (err: Error) => void, timeoutInMs = 0) => { - request.setTimeout(timeoutInMs, () => { - // destroy the request - request.destroy(); - reject(Object.assign(new Error(`Connection timed out after ${timeoutInMs} ms`), { name: "TimeoutError" })); - }); -}; diff --git a/packages/node-http-handler/src/stream-collector/collector.spec.ts b/packages/node-http-handler/src/stream-collector/collector.spec.ts deleted file mode 100644 index cdcb1c612cdf..000000000000 --- a/packages/node-http-handler/src/stream-collector/collector.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Collector } from "./collector"; - -describe("Collector", () => { - const writePromise = (collector: Collector, chunk: any, encoding: BufferEncoding = "utf-8"): Promise => { - return new Promise((resolve, reject) => { - collector.write(chunk, encoding, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - }; - - const listOfBuffers: Buffer[] = [Buffer.from("foo"), Buffer.from("bar"), Buffer.from("buzz")]; - - it("stores a collection of buffers internally", async () => { - const collector = new Collector(); - - await writePromise(collector, listOfBuffers[0]); - await writePromise(collector, listOfBuffers[1]); - await writePromise(collector, listOfBuffers[2]); - collector.end(); - - expect(collector.bufferedBytes.length).toBe(3); - expect(collector.bufferedBytes[0]).toBe(listOfBuffers[0]); - expect(collector.bufferedBytes[1]).toBe(listOfBuffers[1]); - expect(collector.bufferedBytes[2]).toBe(listOfBuffers[2]); - }); -}); diff --git a/packages/node-http-handler/src/stream-collector/collector.ts b/packages/node-http-handler/src/stream-collector/collector.ts deleted file mode 100644 index 455db21ec5d2..000000000000 --- a/packages/node-http-handler/src/stream-collector/collector.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Writable } from "stream"; -export class Collector extends Writable { - public readonly bufferedBytes: Buffer[] = []; - _write(chunk: Buffer, encoding: string, callback: (err?: Error) => void) { - this.bufferedBytes.push(chunk); - callback(); - } -} diff --git a/packages/node-http-handler/src/stream-collector/index.spec.ts b/packages/node-http-handler/src/stream-collector/index.spec.ts deleted file mode 100644 index f9e7c32a7c49..000000000000 --- a/packages/node-http-handler/src/stream-collector/index.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { streamCollector } from "./index"; -import { ReadFromBuffers } from "./readable.mock"; - -describe("streamCollector", () => { - it("returns a Uint8Array containing all data from a stream", async () => { - const mockData = [Buffer.from("foo"), Buffer.from("bar"), Buffer.from("buzz")]; - const mockReadStream = new ReadFromBuffers({ - buffers: mockData, - }); - const expected = new Uint8Array([102, 111, 111, 98, 97, 114, 98, 117, 122, 122]); - const collectedData = await streamCollector(mockReadStream); - expect(collectedData).toEqual(expected); - }); - - it("will propagate errors from the stream", async () => { - // stream should emit an error right away - const mockReadStream = new ReadFromBuffers({ - buffers: [], - errorAfter: 0, - }); - - try { - await streamCollector(mockReadStream); - } catch (err) { - expect(err).toBeDefined(); - } - }); -}); diff --git a/packages/node-http-handler/src/stream-collector/index.ts b/packages/node-http-handler/src/stream-collector/index.ts deleted file mode 100644 index 88f7873e68e5..000000000000 --- a/packages/node-http-handler/src/stream-collector/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { StreamCollector } from "@aws-sdk/types"; -import { Readable } from "stream"; - -import { Collector } from "./collector"; - -export const streamCollector: StreamCollector = (stream: Readable): Promise => - new Promise((resolve, reject) => { - const collector = new Collector(); - stream.pipe(collector); - stream.on("error", (err) => { - // if the source errors, the destination stream needs to manually end - collector.end(); - reject(err); - }); - collector.on("error", reject); - collector.on("finish", function (this: Collector) { - const bytes = new Uint8Array(Buffer.concat(this.bufferedBytes)); - resolve(bytes); - }); - }); diff --git a/packages/node-http-handler/src/stream-collector/readable.mock.ts b/packages/node-http-handler/src/stream-collector/readable.mock.ts deleted file mode 100644 index eec3624809eb..000000000000 --- a/packages/node-http-handler/src/stream-collector/readable.mock.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Readable, ReadableOptions } from "stream"; - -export interface ReadFromBuffersOptions extends ReadableOptions { - buffers: Buffer[]; - errorAfter?: number; -} - -export class ReadFromBuffers extends Readable { - private buffersToRead: Buffer[]; - private numBuffersRead = 0; - - private errorAfter: number; - constructor(options: ReadFromBuffersOptions) { - super(options); - this.buffersToRead = options.buffers; - this.errorAfter = typeof options.errorAfter === "number" ? options.errorAfter : -1; - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _read(size: number) { - if (this.errorAfter !== -1 && this.errorAfter === this.numBuffersRead) { - this.emit("error", new Error("Mock Error")); - return; - } - if (this.numBuffersRead >= this.buffersToRead.length) { - return this.push(null); - } - return this.push(this.buffersToRead[this.numBuffersRead++]); - } -} diff --git a/packages/node-http-handler/src/write-request-body.spec.ts b/packages/node-http-handler/src/write-request-body.spec.ts deleted file mode 100644 index 2847d541ae98..000000000000 --- a/packages/node-http-handler/src/write-request-body.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import EventEmitter from "events"; - -import { writeRequestBody } from "./write-request-body"; - -describe(writeRequestBody.name, () => { - it("should continue on the continue event", async () => { - const emitter = Object.assign(new EventEmitter(), { end() {} }) as any; - const request = { - headers: { expect: "100-continue" }, - body: Buffer.from(""), - end() {}, - } as any; - - const promise = writeRequestBody(emitter, request, 10_000); - emitter.emit("continue", "ok"); - await promise; - }); - - it("should continue on the error event", async () => { - const emitter = Object.assign(new EventEmitter(), { end() {} }) as any; - const request = { - headers: { expect: "100-continue" }, - body: Buffer.from(""), - } as any; - - const promise = writeRequestBody(emitter, request, 10_000); - emitter.emit("error", "uh oh"); - await promise; - }); -}); diff --git a/packages/node-http-handler/src/write-request-body.ts b/packages/node-http-handler/src/write-request-body.ts deleted file mode 100644 index bb018437e318..000000000000 --- a/packages/node-http-handler/src/write-request-body.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { HttpRequest } from "@aws-sdk/types"; -import { ClientRequest } from "http"; -import { ClientHttp2Stream } from "http2"; -import { Readable } from "stream"; - -const MIN_WAIT_TIME = 1000; - -/** - * This resolves when writeBody has been called. - * - * @param httpRequest - opened Node.js request. - * @param request - container with the request body. - * @param maxContinueTimeoutMs - maximum time to wait for the continue event. Minimum of 1000ms. - */ -export async function writeRequestBody( - httpRequest: ClientRequest | ClientHttp2Stream, - request: HttpRequest, - maxContinueTimeoutMs = MIN_WAIT_TIME -): Promise { - const headers = request.headers ?? {}; - const expect = headers["Expect"] || headers["expect"]; - - let timeoutId = -1; - let hasError = false; - - if (expect === "100-continue") { - await Promise.race([ - new Promise((resolve) => { - timeoutId = Number(setTimeout(resolve, Math.max(MIN_WAIT_TIME, maxContinueTimeoutMs))); - }), - new Promise((resolve) => { - httpRequest.on("continue", () => { - clearTimeout(timeoutId); - resolve(); - }); - httpRequest.on("error", () => { - hasError = true; - clearTimeout(timeoutId); - // this handler does not reject with the error - // because there is already an error listener - // on the request in node-http-handler - // and node-http2-handler. - resolve(); - }); - }), - ]); - } - - if (!hasError) { - writeBody(httpRequest, request.body); - } -} - -function writeBody( - httpRequest: ClientRequest | ClientHttp2Stream, - body?: string | ArrayBuffer | ArrayBufferView | Readable | Uint8Array -) { - if (body instanceof Readable) { - // pipe automatically handles end - body.pipe(httpRequest); - } else if (body) { - httpRequest.end(Buffer.from(body as Parameters[0])); - } else { - httpRequest.end(); - } -} diff --git a/packages/property-provider/jest.config.js b/packages/property-provider/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/property-provider/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/property-provider/package.json b/packages/property-provider/package.json index 16a07e9e9fbe..5d84d349c6dd 100644 --- a/packages/property-provider/package.json +++ b/packages/property-provider/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", diff --git a/packages/property-provider/src/CredentialsProviderError.spec.ts b/packages/property-provider/src/CredentialsProviderError.spec.ts deleted file mode 100644 index db951afac61a..000000000000 --- a/packages/property-provider/src/CredentialsProviderError.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CredentialsProviderError } from "./CredentialsProviderError"; -import { ProviderError } from "./ProviderError"; - -describe(CredentialsProviderError.name, () => { - it("should be named as CredentialsProviderError", () => { - expect(new CredentialsProviderError("PANIC").name).toBe("CredentialsProviderError"); - }); - - describe.each([Error, ProviderError, CredentialsProviderError])("should be instanceof %p", (classConstructor) => { - it("when created using constructor", () => { - expect(new CredentialsProviderError("PANIC")).toBeInstanceOf(classConstructor); - }); - - it("when created using from()", () => { - expect(CredentialsProviderError.from(new Error("PANIC"))).toBeInstanceOf(classConstructor); - }); - }); -}); diff --git a/packages/property-provider/src/CredentialsProviderError.ts b/packages/property-provider/src/CredentialsProviderError.ts deleted file mode 100644 index 00c3f23c143e..000000000000 --- a/packages/property-provider/src/CredentialsProviderError.ts +++ /dev/null @@ -1 +0,0 @@ -export { CredentialsProviderError } from "@smithy/property-provider"; diff --git a/packages/property-provider/src/ProviderError.spec.ts b/packages/property-provider/src/ProviderError.spec.ts deleted file mode 100644 index 0067fab17a07..000000000000 --- a/packages/property-provider/src/ProviderError.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ProviderError } from "./ProviderError"; - -describe(ProviderError.name, () => { - it("should be named as ProviderError", () => { - expect(new ProviderError("PANIC").name).toBe("ProviderError"); - }); - - it("should direct the chain to proceed to the next link by default", () => { - expect(new ProviderError("PANIC").tryNextLink).toBe(true); - }); - - it("should allow errors to halt the chain", () => { - expect(new ProviderError("PANIC", false).tryNextLink).toBe(false); - }); - - describe.each([Error, ProviderError])("should be instanceof %p", (classConstructor) => { - it("when created using constructor", () => { - expect(new ProviderError("PANIC")).toBeInstanceOf(classConstructor); - }); - - it("when created using from()", () => { - expect(ProviderError.from(new Error("PANIC"))).toBeInstanceOf(classConstructor); - }); - }); - - it("should create ProviderError from existing error", () => { - const error = new Error("PANIC"); - // @ts-expect-error Property 'someValue' does not exist on type 'Error'. - error.someValue = "foo"; - const providerError = ProviderError.from(error); - // @ts-expect-error Property 'someValue' does not exist on type 'ProviderError'. - expect(providerError.someValue).toBe("foo"); - expect(providerError.tryNextLink).toBe(true); - }); -}); diff --git a/packages/property-provider/src/ProviderError.ts b/packages/property-provider/src/ProviderError.ts deleted file mode 100644 index e6f606aeff65..000000000000 --- a/packages/property-provider/src/ProviderError.ts +++ /dev/null @@ -1 +0,0 @@ -export { ProviderError } from "@smithy/property-provider"; diff --git a/packages/property-provider/src/TokenProviderError.spec.ts b/packages/property-provider/src/TokenProviderError.spec.ts deleted file mode 100644 index d6c067820659..000000000000 --- a/packages/property-provider/src/TokenProviderError.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ProviderError } from "./ProviderError"; -import { TokenProviderError } from "./TokenProviderError"; - -describe(TokenProviderError.name, () => { - it("should be named as TokenProviderError", () => { - expect(new TokenProviderError("PANIC").name).toBe("TokenProviderError"); - }); - - describe.each([Error, ProviderError, TokenProviderError])("should be instanceof %p", (classConstructor) => { - it("when created using constructor", () => { - expect(new TokenProviderError("PANIC")).toBeInstanceOf(classConstructor); - }); - - it("when created using from()", () => { - expect(TokenProviderError.from(new Error("PANIC"))).toBeInstanceOf(classConstructor); - }); - }); -}); diff --git a/packages/property-provider/src/TokenProviderError.ts b/packages/property-provider/src/TokenProviderError.ts deleted file mode 100644 index b1e50b7c7a50..000000000000 --- a/packages/property-provider/src/TokenProviderError.ts +++ /dev/null @@ -1 +0,0 @@ -export { TokenProviderError } from "@smithy/property-provider"; diff --git a/packages/property-provider/src/chain.spec.ts b/packages/property-provider/src/chain.spec.ts deleted file mode 100644 index 816e5a45b717..000000000000 --- a/packages/property-provider/src/chain.spec.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { chain } from "./chain"; -import { ProviderError } from "./ProviderError"; - -const resolveStatic = (staticValue: unknown) => jest.fn().mockResolvedValue(staticValue); -const rejectWithError = (errorMsg: string) => jest.fn().mockRejectedValue(new Error(errorMsg)); -const rejectWithProviderError = (errorMsg: string) => jest.fn().mockRejectedValue(new ProviderError(errorMsg)); - -describe("chain", () => { - it("should distill many credential providers into one", async () => { - const provider = chain(resolveStatic("foo"), resolveStatic("bar")); - expect(typeof (await provider())).toBe("string"); - }); - - it("should return the resolved value of the first successful promise", async () => { - const expectedOutput = "foo"; - const providers = [ - rejectWithProviderError("Move along"), - rejectWithProviderError("Nothing to see here"), - resolveStatic(expectedOutput), - ]; - - try { - const result = await chain(...providers)(); - expect(result).toBe(expectedOutput); - } catch (error) { - throw error; - } - - expect(providers[0]).toHaveBeenCalledTimes(1); - expect(providers[1]).toHaveBeenCalledTimes(1); - expect(providers[2]).toHaveBeenCalledTimes(1); - }); - - it("should not invoke subsequent providers once one resolves", async () => { - const expectedOutput = "foo"; - const providers = [ - rejectWithProviderError("Move along"), - resolveStatic(expectedOutput), - rejectWithProviderError("This provider should not be invoked"), - ]; - - try { - const result = await chain(...providers)(); - expect(result).toBe(expectedOutput); - } catch (error) { - throw error; - } - - expect(providers[0]).toHaveBeenCalledTimes(1); - expect(providers[1]).toHaveBeenCalledTimes(1); - expect(providers[2]).not.toHaveBeenCalled(); - }); - - describe("should throw if no provider resolves", () => { - const expectedErrorMsg = "Last provider failed"; - - it.each([ - [ProviderError, rejectWithProviderError(expectedErrorMsg)], - [Error, rejectWithError(expectedErrorMsg)], - ])("case %p", async (errorType, errorProviderMockFn) => { - const firstProviderWhichRejects = rejectWithProviderError("Move along"); - try { - await chain(firstProviderWhichRejects, errorProviderMockFn)(); - throw new Error("Should not get here"); - } catch (error) { - expect(error).toEqual(new errorType(expectedErrorMsg)); - } - expect(firstProviderWhichRejects).toHaveBeenCalledTimes(1); - expect(errorProviderMockFn).toHaveBeenCalledTimes(1); - }); - }); - - it("should halt if an unrecognized error is encountered", async () => { - const expectedErrorMsg = "Unrelated failure"; - const providers = [rejectWithProviderError("Move along"), rejectWithError(expectedErrorMsg), resolveStatic("foo")]; - - try { - await chain(...providers)(); - throw new Error("Should not get here"); - } catch (error) { - expect(error).toEqual(new Error(expectedErrorMsg)); - } - - expect(providers[0]).toHaveBeenCalledTimes(1); - expect(providers[1]).toHaveBeenCalledTimes(1); - expect(providers[2]).not.toHaveBeenCalled(); - }); - - it("should halt if ProviderError explicitly requests it", async () => { - const expectedError = new ProviderError("ProviderError with tryNextLink set to false", false); - const providers = [ - rejectWithProviderError("Move along"), - jest.fn().mockRejectedValue(expectedError), - resolveStatic("foo"), - ]; - - try { - await chain(...providers)(); - throw new Error("Should not get here"); - } catch (error) { - expect(error).toEqual(expectedError); - } - - expect(providers[0]).toHaveBeenCalledTimes(1); - expect(providers[1]).toHaveBeenCalledTimes(1); - expect(providers[2]).not.toHaveBeenCalled(); - }); - - it("should reject chains with no links", async () => { - try { - await chain()(); - throw new Error("Should not get here"); - } catch (error) { - expect(error).toEqual(new Error("No providers in chain")); - } - }); -}); diff --git a/packages/property-provider/src/chain.ts b/packages/property-provider/src/chain.ts deleted file mode 100755 index eb521001decc..000000000000 --- a/packages/property-provider/src/chain.ts +++ /dev/null @@ -1 +0,0 @@ -export { chain } from "@smithy/property-provider"; diff --git a/packages/property-provider/src/fromStatic.spec.ts b/packages/property-provider/src/fromStatic.spec.ts deleted file mode 100644 index 72f43d3d7588..000000000000 --- a/packages/property-provider/src/fromStatic.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { fromStatic } from "./fromStatic"; - -describe("fromStatic", () => { - it("should convert a static value into a provider", async () => { - const staticValue = "staticValue"; - const provider = fromStatic(staticValue); - return expect(provider()).resolves.toStrictEqual(staticValue); - }); - - it("should always return the same promise", () => { - const provider = fromStatic("string"); - const result = provider(); - - Array.from({ length: 5 }).forEach(() => { - expect(provider()).toStrictEqual(result); - }); - }); -}); diff --git a/packages/property-provider/src/fromStatic.ts b/packages/property-provider/src/fromStatic.ts deleted file mode 100644 index 1aeeca2f547e..000000000000 --- a/packages/property-provider/src/fromStatic.ts +++ /dev/null @@ -1 +0,0 @@ -export { fromStatic } from "@smithy/property-provider"; diff --git a/packages/property-provider/src/index.ts b/packages/property-provider/src/index.ts index 15d14e5b6437..e081442dc3b0 100644 --- a/packages/property-provider/src/index.ts +++ b/packages/property-provider/src/index.ts @@ -1,6 +1 @@ -export * from "./CredentialsProviderError"; -export * from "./ProviderError"; -export * from "./TokenProviderError"; -export * from "./chain"; -export * from "./fromStatic"; -export * from "./memoize"; +export * from "@smithy/property-provider"; diff --git a/packages/property-provider/src/memoize.spec.ts b/packages/property-provider/src/memoize.spec.ts deleted file mode 100644 index 3bf1fb05fcff..000000000000 --- a/packages/property-provider/src/memoize.spec.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { memoize } from "./memoize"; - -describe("memoize", () => { - let provider: jest.Mock; - const mockReturn = "foo"; - const repeatTimes = 10; - - beforeEach(() => { - provider = jest.fn().mockResolvedValue(mockReturn); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("static memoization", () => { - it("should cache the resolved provider", async () => { - expect.assertions(repeatTimes * 2 + 1); - - const memoized = memoize(provider); - expect(provider).toHaveBeenCalledTimes(0); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const _ in [...Array(repeatTimes).keys()]) { - expect(await memoized()).toStrictEqual(mockReturn); - expect(provider).toHaveBeenCalledTimes(1); - } - }); - - it("should not make extra request for concurrent calls", async () => { - const memoized = memoize(provider); - const results = await Promise.all([...Array(repeatTimes).keys()].map(() => memoized())); - expect(provider).toHaveBeenCalledTimes(1); - for (const res of results) { - expect(res).toStrictEqual(mockReturn); - } - }); - - it("should retry provider if previous provider is failed", async () => { - provider - .mockReset() - .mockRejectedValueOnce("Error") - .mockResolvedValueOnce("Retry") - .mockRejectedValueOnce("Should not call 3rd time"); - const memoized = memoize(provider); - try { - await memoized(); - fail(); - } catch (e) { - expect(e).toBe("Error"); - } - expect(await memoized()).toBe("Retry"); - expect(await memoized()).toBe("Retry"); - expect(provider).toBeCalledTimes(2); - }); - - it("should retry provider if forceRefresh parameter is used", async () => { - provider - .mockReset() - .mockResolvedValueOnce("1st") - .mockResolvedValueOnce("2nd") - .mockRejectedValueOnce("Should not call 3rd time"); - const memoized = memoize(provider); - expect(await memoized()).toBe("1st"); - expect(await memoized()).toBe("1st"); - expect(await memoized({ forceRefresh: true })).toBe("2nd"); - expect(await memoized()).toBe("2nd"); - expect(provider).toBeCalledTimes(2); - }); - }); - - describe("refreshing memoization", () => { - let isExpired: jest.Mock; - let requiresRefresh: jest.Mock; - - beforeEach(() => { - isExpired = jest.fn().mockReturnValue(true); - requiresRefresh = jest.fn().mockReturnValue(false); - }); - - describe("should not reinvoke the underlying provider while isExpired returns `false`", () => { - const isExpiredFalseTest = async (requiresRefresh?: any) => { - isExpired.mockReturnValue(false); - const memoized = memoize(provider, isExpired, requiresRefresh); - expect(provider).toHaveBeenCalledTimes(0); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const index in [...Array(repeatTimes).keys()]) { - expect(await memoized()).toEqual(mockReturn); - } - - expect(isExpired).toHaveBeenCalledTimes(repeatTimes); - if (requiresRefresh) { - expect(requiresRefresh).toHaveBeenCalledTimes(repeatTimes); - } - expect(provider).toHaveBeenCalledTimes(1); - }; - - it("when requiresRefresh is not passed", async () => { - return isExpiredFalseTest(); - }); - - it("when requiresRefresh returns true", () => { - requiresRefresh.mockReturnValue(true); - return isExpiredFalseTest(requiresRefresh); - }); - }); - - describe("should reinvoke the underlying provider when isExpired returns `true`", () => { - const isExpiredTrueTest = async (requiresRefresh?: any) => { - const memoized = memoize(provider, isExpired, requiresRefresh); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const index in [...Array(repeatTimes).keys()]) { - expect(await memoized()).toEqual(mockReturn); - } - - expect(isExpired).toHaveBeenCalledTimes(repeatTimes); - if (requiresRefresh) { - expect(requiresRefresh).toHaveBeenCalledTimes(repeatTimes); - } - expect(provider).toHaveBeenCalledTimes(repeatTimes + 1); - }; - - it("when requiresRefresh is not passed", () => { - return isExpiredTrueTest(); - }); - - it("when requiresRefresh returns true", () => { - requiresRefresh.mockReturnValue(true); - return isExpiredTrueTest(requiresRefresh); - }); - }); - - describe("when called with forceRefresh set to `true`", () => { - it("should reinvoke the underlying provider even if isExpired returns false", async () => { - const memoized = memoize(provider, isExpired, requiresRefresh); - isExpired.mockReturnValue(false); - for (const _ in [...Array(repeatTimes).keys()]) { - expect(await memoized({ forceRefresh: true })).toEqual(mockReturn); - } - expect(provider).toHaveBeenCalledTimes(repeatTimes); - }); - - it("should reinvoke the underlying provider even if requiresRefresh returns false", async () => { - const memoized = memoize(provider, isExpired, requiresRefresh); - requiresRefresh.mockReturnValue(false); - for (const _ in [...Array(repeatTimes).keys()]) { - expect(await memoized({ forceRefresh: true })).toEqual(mockReturn); - } - expect(provider).toHaveBeenCalledTimes(repeatTimes); - }); - }); - - describe("when `requiresRefresh` returns `false`", () => { - const requiresRefreshFalseTest = async () => { - const memoized = memoize(provider, isExpired, requiresRefresh); - const result = memoized(); - expect(await result).toBe(mockReturn); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const index in [...Array(repeatTimes).keys()]) { - expect(memoized()).toStrictEqual(result); - expect(provider).toHaveBeenCalledTimes(1); - } - - expect(requiresRefresh).toHaveBeenCalledTimes(1); - expect(isExpired).not.toHaveBeenCalled(); - }; - - it("should return the same promise for invocations 2-infinity if isExpired returns true", () => { - return requiresRefreshFalseTest(); - }); - - it("should return the same promise for invocations 2-infinity if isExpired returns false", () => { - isExpired.mockReturnValue(false); - return requiresRefreshFalseTest(); - }); - - it("should re-evaluate `requiresRefresh` after force refresh", async () => { - const memoized = memoize(provider, isExpired, requiresRefresh); - for (const _ in [...Array(repeatTimes).keys()]) { - expect(await memoized({ forceRefresh: true })).toStrictEqual(mockReturn); - } - expect(requiresRefresh).toBeCalledTimes(repeatTimes); - }); - }); - - describe("should not make extra request for concurrent calls", () => { - const requiresRefreshFalseTest = async () => { - const memoized = memoize(provider, isExpired, requiresRefresh); - const results = await Promise.all([...Array(repeatTimes).keys()].map(() => memoized())); - expect(provider).toHaveBeenCalledTimes(1); - for (const res of results) { - expect(res).toStrictEqual(mockReturn); - } - }; - - it("when isExpired returns true", () => { - return requiresRefreshFalseTest(); - }); - - it("when isExpired returns false", () => { - isExpired.mockReturnValue(false); - return requiresRefreshFalseTest(); - }); - }); - - it("should retry provider if previous provider is failed", async () => { - provider - .mockReset() - .mockRejectedValueOnce("Error") - .mockResolvedValueOnce("Retry") - .mockRejectedValueOnce("Should not call 3rd time"); - isExpired.mockReset().mockReturnValue(false); - const memoized = memoize(provider, isExpired); - try { - await memoized(); - fail(); - } catch (e) { - expect(e).toBe("Error"); - } - expect(await memoized()).toBe("Retry"); - expect(await memoized()).toBe("Retry"); - expect(provider).toBeCalledTimes(2); - }); - }); -}); diff --git a/packages/property-provider/src/memoize.ts b/packages/property-provider/src/memoize.ts deleted file mode 100755 index 90b021aaca1c..000000000000 --- a/packages/property-provider/src/memoize.ts +++ /dev/null @@ -1 +0,0 @@ -export { memoize } from "@smithy/property-provider"; diff --git a/packages/protocol-http/jest.config.js b/packages/protocol-http/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/protocol-http/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/protocol-http/package.json b/packages/protocol-http/package.json index 7b5824dd48d1..f84f67219170 100644 --- a/packages/protocol-http/package.json +++ b/packages/protocol-http/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -21,7 +21,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "*", + "@smithy/protocol-http": "^1.1.0", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/protocol-http/src/Field.ts b/packages/protocol-http/src/Field.ts deleted file mode 100644 index 3fe5d90862a6..000000000000 --- a/packages/protocol-http/src/Field.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { FieldPosition } from "./FieldPosition"; - -export type FieldOptions = { - name: string; - kind?: FieldPosition; - values?: string[]; -}; - -/** - * A name-value pair representing a single field - * transmitted in an HTTP Request or Response. - * - * The kind will dictate metadata placement within - * an HTTP message. - * - * All field names are case insensitive and - * case-variance must be treated as equivalent. - * Names MAY be normalized but SHOULD be preserved - * for accuracy during transmission. - */ -export class Field { - public readonly name: string; - public readonly kind: FieldPosition; - - public values: string[]; - - constructor({ name, kind = FieldPosition.HEADER, values = [] }: FieldOptions) { - this.name = name; - this.kind = kind; - this.values = values; - } - - /** - * Appends a value to the field. - * - * @param value The value to append. - */ - public add(value: string): void { - this.values.push(value); - } - - /** - * Overwrite existing field values. - * - * @param values The new field values. - */ - public set(values: string[]): void { - this.values = values; - } - - /** - * Remove all matching entries from list. - * - * @param value Value to remove. - */ - public remove(value: string): void { - this.values = this.values.filter((v) => v !== value); - } - - /** - * Get comma-delimited string. - * - * @returns String representation of {@link Field}. - */ - public toString(): string { - // Values with spaces or commas MUST be double-quoted - return this.values.map((v) => (v.includes(",") || v.includes(" ") ? `"${v}"` : v)).join(", "); - } - - /** - * Get string values as a list - * - * @returns Values in {@link Field} as a list. - */ - public get(): string[] { - return this.values; - } -} diff --git a/packages/protocol-http/src/FieldPosition.ts b/packages/protocol-http/src/FieldPosition.ts deleted file mode 100644 index c1daf885dd12..000000000000 --- a/packages/protocol-http/src/FieldPosition.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum FieldPosition { - HEADER, - TRAILER, -} diff --git a/packages/protocol-http/src/Fields.ts b/packages/protocol-http/src/Fields.ts deleted file mode 100644 index 0632819b8596..000000000000 --- a/packages/protocol-http/src/Fields.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Field } from "./Field"; -import { FieldPosition } from "./FieldPosition"; - -export type FieldsOptions = { fields?: Field[]; encoding?: string }; - -/** - * Collection of Field entries mapped by name. - */ -export class Fields { - private readonly entries: Record = {}; - private readonly encoding: string; - - constructor({ fields = [], encoding = "utf-8" }: FieldsOptions) { - fields.forEach(this.setField.bind(this)); - this.encoding = encoding; - } - - /** - * Set entry for a {@link Field} name. The `name` - * attribute will be used to key the collection. - * - * @param field The {@link Field} to set. - */ - public setField(field: Field): void { - this.entries[field.name.toLowerCase()] = field; - } - - /** - * Retrieve {@link Field} entry by name. - * - * @param name The name of the {@link Field} entry - * to retrieve - * @returns The {@link Field} if it exists. - */ - public getField(name: string): Field | undefined { - return this.entries[name.toLowerCase()]; - } - - /** - * Delete entry from collection. - * - * @param name Name of the entry to delete. - */ - public removeField(name: string): void { - delete this.entries[name.toLowerCase()]; - } - - /** - * Helper function for retrieving specific types of fields. - * Used to grab all headers or all trailers. - * - * @param kind {@link FieldPosition} of entries to retrieve. - * @returns The {@link Field} entries with the specified - * {@link FieldPosition}. - */ - public getByType(kind: FieldPosition): Field[] { - return Object.values(this.entries).filter((field) => field.kind === kind); - } -} diff --git a/packages/protocol-http/src/httpHandler.ts b/packages/protocol-http/src/httpHandler.ts deleted file mode 100644 index 78af380f9be5..000000000000 --- a/packages/protocol-http/src/httpHandler.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { HttpHandlerOptions, RequestHandler } from "@aws-sdk/types"; - -import { HttpRequest } from "./httpRequest"; -import { HttpResponse } from "./httpResponse"; - -export type HttpHandler = RequestHandler; diff --git a/packages/protocol-http/src/httpRequest.ts b/packages/protocol-http/src/httpRequest.ts deleted file mode 100644 index f8bcde17b828..000000000000 --- a/packages/protocol-http/src/httpRequest.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { HeaderBag, HttpMessage, HttpRequest as IHttpRequest, QueryParameterBag, URI } from "@aws-sdk/types"; - -type HttpRequestOptions = Partial & Partial & { method?: string }; - -export interface HttpRequest extends IHttpRequest {} - -export class HttpRequest implements HttpMessage, URI { - public method: string; - public protocol: string; - public hostname: string; - public port?: number; - public path: string; - public query: QueryParameterBag; - public headers: HeaderBag; - public username?: string; - public password?: string; - public fragment?: string; - public body?: any; - - constructor(options: HttpRequestOptions) { - this.method = options.method || "GET"; - this.hostname = options.hostname || "localhost"; - this.port = options.port; - this.query = options.query || {}; - this.headers = options.headers || {}; - this.body = options.body; - this.protocol = options.protocol - ? options.protocol.slice(-1) !== ":" - ? `${options.protocol}:` - : options.protocol - : "https:"; - this.path = options.path ? (options.path.charAt(0) !== "/" ? `/${options.path}` : options.path) : "/"; - this.username = options.username; - this.password = options.password; - this.fragment = options.fragment; - } - - static isInstance(request: unknown): request is HttpRequest { - //determine if request is a valid httpRequest - if (!request) return false; - const req: any = request; - return ( - "method" in req && - "protocol" in req && - "hostname" in req && - "path" in req && - typeof req["query"] === "object" && - typeof req["headers"] === "object" - ); - } - - clone(): HttpRequest { - const cloned = new HttpRequest({ - ...this, - headers: { ...this.headers }, - }); - if (cloned.query) cloned.query = cloneQuery(cloned.query); - return cloned; - } -} - -function cloneQuery(query: QueryParameterBag): QueryParameterBag { - return Object.keys(query).reduce((carry: QueryParameterBag, paramName: string) => { - const param = query[paramName]; - return { - ...carry, - [paramName]: Array.isArray(param) ? [...param] : param, - }; - }, {}); -} diff --git a/packages/protocol-http/src/httpResponse.ts b/packages/protocol-http/src/httpResponse.ts deleted file mode 100644 index adca1cba94e9..000000000000 --- a/packages/protocol-http/src/httpResponse.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { HeaderBag, HttpMessage, HttpResponse as IHttpResponse } from "@aws-sdk/types"; - -type HttpResponseOptions = Partial & { - statusCode: number; - reason?: string; -}; - -export interface HttpResponse extends IHttpResponse {} - -export class HttpResponse { - public statusCode: number; - public reason?: string; - public headers: HeaderBag; - public body?: any; - - constructor(options: HttpResponseOptions) { - this.statusCode = options.statusCode; - this.reason = options.reason; - this.headers = options.headers || {}; - this.body = options.body; - } - - static isInstance(response: unknown): response is HttpResponse { - //determine if response is a valid HttpResponse - if (!response) return false; - const resp = response as any; - return typeof resp.statusCode === "number" && typeof resp.headers === "object"; - } -} diff --git a/packages/protocol-http/src/index.ts b/packages/protocol-http/src/index.ts index facc46f0a356..a6a99bf3a1da 100644 --- a/packages/protocol-http/src/index.ts +++ b/packages/protocol-http/src/index.ts @@ -1,7 +1 @@ -export * from "./Field"; -export * from "./FieldPosition"; -export * from "./Fields"; -export * from "./httpHandler"; -export * from "./httpRequest"; -export * from "./httpResponse"; -export * from "./isValidHostname"; +export * from "@smithy/protocol-http"; diff --git a/packages/protocol-http/src/isValidHostname.spec.ts b/packages/protocol-http/src/isValidHostname.spec.ts deleted file mode 100644 index aa280d55cf28..000000000000 --- a/packages/protocol-http/src/isValidHostname.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { isValidHostname } from "./isValidHostname"; - -describe("implementation selection", () => { - it("should return true for valid hostnames", () => { - const validHostnames = ["foo", "foo.bar", "1foo.bar", "foo.bar1"]; - for (const hostname of validHostnames) { - expect(isValidHostname(hostname)).toBe(true); - } - }); - - it("should return false for invalid hostnames", () => { - const invalidHostnames = ["foo.com/?bar", ".foo"]; - for (const hostname of invalidHostnames) { - expect(isValidHostname(hostname)).toBe(false); - } - }); -}); diff --git a/packages/protocol-http/src/isValidHostname.ts b/packages/protocol-http/src/isValidHostname.ts deleted file mode 100644 index 2f9fdfb1f591..000000000000 --- a/packages/protocol-http/src/isValidHostname.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function isValidHostname(hostname: string): boolean { - const hostPattern = /^[a-z0-9][a-z0-9\.\-]*[a-z0-9]$/; - return hostPattern.test(hostname); -} diff --git a/packages/querystring-builder/jest.config.js b/packages/querystring-builder/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/querystring-builder/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/querystring-builder/package.json b/packages/querystring-builder/package.json index 0cbb87d5dbb5..0650a929adff 100644 --- a/packages/querystring-builder/package.json +++ b/packages/querystring-builder/package.json @@ -20,8 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "*", - "@aws-sdk/util-uri-escape": "*", + "@smithy/querystring-builder": "^1.0.1", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/querystring-builder/src/index.ts b/packages/querystring-builder/src/index.ts index 77e92e36d9ed..8c81b185667f 100644 --- a/packages/querystring-builder/src/index.ts +++ b/packages/querystring-builder/src/index.ts @@ -1,26 +1 @@ -import { QueryParameterBag } from "@aws-sdk/types"; -import { escapeUri } from "@aws-sdk/util-uri-escape"; - -/** - * @internal - */ -export function buildQueryString(query: QueryParameterBag): string { - const parts: string[] = []; - for (let key of Object.keys(query).sort()) { - const value = query[key]; - key = escapeUri(key); - if (Array.isArray(value)) { - for (let i = 0, iLen = value.length; i < iLen; i++) { - parts.push(`${key}=${escapeUri(value[i])}`); - } - } else { - let qsEntry = key; - if (value || typeof value === "string") { - qsEntry += `=${escapeUri(value)}`; - } - parts.push(qsEntry); - } - } - - return parts.join("&"); -} +export * from "@smithy/querystring-builder"; diff --git a/packages/querystring-parser/jest.config.js b/packages/querystring-parser/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/querystring-parser/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/querystring-parser/package.json b/packages/querystring-parser/package.json index d7cee2f84e31..3b6201c8dab9 100644 --- a/packages/querystring-parser/package.json +++ b/packages/querystring-parser/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,7 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "*", + "@smithy/querystring-parser": "^1.0.1", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/querystring-parser/src/index.spec.ts b/packages/querystring-parser/src/index.spec.ts deleted file mode 100644 index a3edf2cc7d85..000000000000 --- a/packages/querystring-parser/src/index.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { QueryParameterBag } from "@aws-sdk/types"; - -import { parseQueryString } from "./"; - -describe("parseQueryString", () => { - const testCases = new Map([ - [ - "?snap=cr%C3%A4ckle&snap=p%C3%B4p&fizz=buzz&quux", - { - snap: ["cräckle", "pôp"], - fizz: "buzz", - quux: null, - }, - ], - ["?", {}], - ["?foo=", { foo: "" }], - ["foo=bar&foo=baz&foo=quux", { foo: ["bar", "baz", "quux"] }], - ]); - - for (const [querystring, parsed] of testCases) { - it(`should correctly parse ${querystring}`, () => { - expect(parseQueryString(querystring)).toEqual(parsed); - }); - } -}); diff --git a/packages/querystring-parser/src/index.ts b/packages/querystring-parser/src/index.ts index 82672d3b92b8..f5beeca230fc 100644 --- a/packages/querystring-parser/src/index.ts +++ b/packages/querystring-parser/src/index.ts @@ -1,28 +1 @@ -import { QueryParameterBag } from "@aws-sdk/types"; - -/** - * @internal - */ -export function parseQueryString(querystring: string): QueryParameterBag { - const query: QueryParameterBag = {}; - querystring = querystring.replace(/^\?/, ""); - - if (querystring) { - for (const pair of querystring.split("&")) { - let [key, value = null] = pair.split("="); - key = decodeURIComponent(key); - if (value) { - value = decodeURIComponent(value); - } - if (!(key in query)) { - query[key] = value; - } else if (Array.isArray(query[key])) { - (query[key] as Array).push(value as string); - } else { - query[key] = [query[key] as string, value as string]; - } - } - } - - return query; -} +export * from "@smithy/querystring-parser"; diff --git a/packages/service-client-documentation-generator/package.json b/packages/service-client-documentation-generator/package.json index dacfe0a7c6d0..9e3b67c99da2 100644 --- a/packages/service-client-documentation-generator/package.json +++ b/packages/service-client-documentation-generator/package.json @@ -23,6 +23,7 @@ "typedocplugin" ], "dependencies": { + "@smithy/service-client-documentation-generator": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/service-client-documentation-generator/src/index.ts b/packages/service-client-documentation-generator/src/index.ts index 3da32d4b4c35..82494a8b3bb8 100644 --- a/packages/service-client-documentation-generator/src/index.ts +++ b/packages/service-client-documentation-generator/src/index.ts @@ -1,17 +1 @@ -import { Application, ParameterType } from "typedoc"; - -import { SdkClientTocPlugin } from "./sdk-client-toc-plugin"; - -/** - * @internal - */ -export function load(app: Application) { - app.options.addDeclaration({ - name: "defaultGroup", - help: "Default group to place categories as children", - defaultValue: "SDK", - type: ParameterType.String, - }); - - new SdkClientTocPlugin(app.options, app.logger, app.renderer); -} +export * from "@smithy/service-client-documentation-generator"; diff --git a/packages/service-client-documentation-generator/src/sdk-client-toc-plugin.ts b/packages/service-client-documentation-generator/src/sdk-client-toc-plugin.ts deleted file mode 100644 index 679a0d75c7b7..000000000000 --- a/packages/service-client-documentation-generator/src/sdk-client-toc-plugin.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { dirname } from "path"; -import { - BindOption, - ContainerReflection, - Context, - Converter, - DeclarationReflection, - Logger, - Options, - ProjectReflection, - ReferenceType, - ReflectionCategory, - ReflectionFlag, - ReflectionGroup, - ReflectionKind, - Renderer, -} from "typedoc"; - -import { isClientModel } from "./utils"; - -/** - * @internal - * - * Group the ToC for easier observability. - */ -export class SdkClientTocPlugin { - private clientDir?: string; - - @BindOption("defaultGroup") - readonly defaultGroup: string; - - @BindOption("defaultCategory") - readonly defaultCategory: string; - - constructor(public readonly options: Options, public readonly logger: Logger, private readonly renderer: Renderer) { - this.renderer.application.converter.on(Converter.EVENT_END, this.changeLinksToLowerCase); - this.renderer.application.converter.on(Converter.EVENT_RESOLVE_END, this.onEndResolve); - } - - private changeLinksToLowerCase = (context: Context) => { - Object.keys(context.project.reflections).forEach((reflectionName) => { - context.project.reflections[reflectionName]._alias = context.project.reflections[reflectionName] - .getAlias() - .toLowerCase(); - }); - }; - - private onEndResolve = (context: Context) => { - if (!this.clientDir) this.clientDir = this.loadClientDir(context.project); - for (const model of Object.values(context.project.reflections)) { - const isEffectiveParent = (model instanceof ContainerReflection && model.children?.length) || model.isProject(); - if (!isEffectiveParent || model.kindOf(ReflectionKind.SomeModule)) { - return; - } - - if (!model.groups) { - model.groups = []; - } - - let group = model.groups.find((value) => value.title === this.defaultGroup); - - if (!group) { - group = new ReflectionGroup(this.defaultGroup); - model.groups.push(group); - } - - group.categories = this.defineCategories(group, model.children); - - const modulesIndex = model.groups.findIndex((value) => value.title === "Modules"); - // Removing `Modules` group from array - if (modulesIndex >= 0) { - model.groups.splice(modulesIndex, 1); - } - } - }; - - // Confirm declaration comes from the same folder as the client class - private belongsToClientPackage(model: DeclarationReflection): boolean { - return this.clientDir && model.sources?.[0].fullFileName.indexOf(this.clientDir) === 0; - } - - private isClient(model: DeclarationReflection): boolean { - const { extendedTypes = [] } = model; - return ( - model.kindOf(ReflectionKind.Class) && - model.getFullName() !== "Client" && // Exclude the Smithy Client class. - (model.name.endsWith("Client") /* Modular client like S3Client */ || - extendedTypes.filter((reference) => (reference as ReferenceType).name === `${model.name}Client`).length > 0) && - /* Filter out other client classes that not sourced from the same directory as current client. e.g. STS, SSO */ - this.belongsToClientPackage(model) - ); - } - - private isCommand(model: DeclarationReflection): boolean { - return ( - model.kindOf(ReflectionKind.Class) && - model.name.endsWith("Command") && - // model.children?.some((child) => child.name === "resolveMiddleware") && - this.belongsToClientPackage(model) - ); - } - - private isPaginator(model: DeclarationReflection): boolean { - return ( - model.name.startsWith("paginate") && model.kindOf(ReflectionKind.Function) && this.belongsToClientPackage(model) - ); - } - - private isInputOrOutput(model: DeclarationReflection): boolean { - return ( - model.kindOf(ReflectionKind.Interface) && - (model.name.endsWith("CommandInput") || model.name.endsWith("CommandOutput")) && - this.belongsToClientPackage(model) - ); - } - - private isWaiter(model: DeclarationReflection): boolean { - return ( - model.name.startsWith("waitFor") && model.kindOf(ReflectionKind.Function) && this.belongsToClientPackage(model) - ); - } - /** - * Define navigation categories in Client, Commands, Paginators and Waiters sections. It will update the - * supplied categories array. - * - * @param group The parent group where the categories will be placed under. - * @param reflections The reflections that should be categorized. - */ - private defineCategories(group: ReflectionGroup, reflections: DeclarationReflection[]): ReflectionCategory[] { - const categories = group.categories || []; - if (this.isCategorized(categories)) return group.categories; - - const clients = new ReflectionCategory("Clients"); - const commands = new ReflectionCategory("Commands"); - const paginators = new ReflectionCategory("Paginators"); - const waiters = new ReflectionCategory("Waiters"); - reflections.forEach((reflection: DeclarationReflection) => { - if (reflection.kindOf(ReflectionKind.SomeModule)) { - return; - } - - if (this.isClient(reflection)) { - clients.children.push(reflection); - reflection.flags.setFlag(ReflectionFlag.Public, false); - } else if (this.isCommand(reflection)) { - commands.children.push(reflection); - reflection.flags.setFlag(ReflectionFlag.Protected, true); - } else if (this.isPaginator(reflection)) { - paginators.children.push(reflection); - reflection.flags.setFlag(ReflectionFlag.Protected, true); - } else if (this.isInputOrOutput(reflection)) { - commands.children.push(reflection); - reflection.flags.setFlag(ReflectionFlag.Protected, true); - } else if (this.isWaiter(reflection)) { - waiters.children.push(reflection); - reflection.flags.setFlag(ReflectionFlag.Protected, true); - } - }); - // Group commands and input/output interface of each command. - commands.children.sort((childA, childB) => childA.name.localeCompare(childB.name)); - - categories.push(...[clients, commands, paginators, waiters]); - return categories; - } - - private isCategorized(categories: ReflectionCategory[]): boolean { - const childrenNames = categories.map((child) => child.title); - return ( - childrenNames.includes("Clients") && - childrenNames.includes("Commands") && - childrenNames.includes("Paginators") && - childrenNames.includes("Waiters") - ); - } - - private loadClientDir(project: ProjectReflection) { - const children = Object.values(project.reflections).filter(isClientModel); - const fullFileName = children.find((child) => child.sources[0].fileName.endsWith("Client.ts")).sources[0] - .fullFileName; - return dirname(dirname(fullFileName)); - } -} diff --git a/packages/service-client-documentation-generator/src/utils.ts b/packages/service-client-documentation-generator/src/utils.ts deleted file mode 100644 index 2b007bcd44b4..000000000000 --- a/packages/service-client-documentation-generator/src/utils.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { sep } from "path"; -import { Reflection } from "typedoc"; - -/** - * @internal - */ -export const isClientModel = (model: Reflection | undefined) => - model?.sources?.[0]?.fullFileName.includes(`${sep}clients${sep}`); diff --git a/packages/service-error-classification/jest.config.js b/packages/service-error-classification/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/service-error-classification/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/service-error-classification/package.json b/packages/service-error-classification/package.json index a09e5ed65c67..3a8a46091d09 100644 --- a/packages/service-error-classification/package.json +++ b/packages/service-error-classification/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -19,6 +19,9 @@ "url": "https://aws.amazon.com/javascript/" }, "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^1.0.2" + }, "devDependencies": { "@aws-sdk/types": "*", "@tsconfig/recommended": "1.0.1", diff --git a/packages/service-error-classification/src/constants.ts b/packages/service-error-classification/src/constants.ts deleted file mode 100644 index 80a9fb2916c8..000000000000 --- a/packages/service-error-classification/src/constants.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Errors encountered when the client clock and server clock cannot agree on the - * current time. - * - * These errors are retryable, assuming the SDK has enabled clock skew - * correction. - */ -export const CLOCK_SKEW_ERROR_CODES = [ - "AuthFailure", - "InvalidSignatureException", - "RequestExpired", - "RequestInTheFuture", - "RequestTimeTooSkewed", - "SignatureDoesNotMatch", -]; - -/** - * Errors that indicate the SDK is being throttled. - * - * These errors are always retryable. - */ -export const THROTTLING_ERROR_CODES = [ - "BandwidthLimitExceeded", - "EC2ThrottledException", - "LimitExceededException", - "PriorRequestNotComplete", - "ProvisionedThroughputExceededException", - "RequestLimitExceeded", - "RequestThrottled", - "RequestThrottledException", - "SlowDown", - "ThrottledException", - "Throttling", - "ThrottlingException", - "TooManyRequestsException", - "TransactionInProgressException", // DynamoDB -]; - -/** - * Error codes that indicate transient issues - */ -export const TRANSIENT_ERROR_CODES = ["TimeoutError", "RequestTimeout", "RequestTimeoutException"]; - -/** - * Error codes that indicate transient issues - */ -export const TRANSIENT_ERROR_STATUS_CODES = [500, 502, 503, 504]; - -/** - * Node.js system error codes that indicate timeout. - */ -export const NODEJS_TIMEOUT_ERROR_CODES = ["ECONNRESET", "ECONNREFUSED", "EPIPE", "ETIMEDOUT"]; diff --git a/packages/service-error-classification/src/index.spec.ts b/packages/service-error-classification/src/index.spec.ts deleted file mode 100644 index 037926d4ad5e..000000000000 --- a/packages/service-error-classification/src/index.spec.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { RetryableTrait, SdkError } from "@aws-sdk/types"; - -import { - CLOCK_SKEW_ERROR_CODES, - THROTTLING_ERROR_CODES, - TRANSIENT_ERROR_CODES, - TRANSIENT_ERROR_STATUS_CODES, -} from "./constants"; -import { isClockSkewError, isRetryableByTrait, isServerError, isThrottlingError, isTransientError } from "./index"; - -const checkForErrorType = ( - isErrorTypeFunc: (error: SdkError) => boolean, - options: { - name?: string; - httpStatusCode?: number; - $retryable?: RetryableTrait; - }, - errorTypeResult: boolean -) => { - const { name, httpStatusCode, $retryable } = options; - const error = Object.assign(new Error(), { - name, - $metadata: { httpStatusCode }, - $retryable, - }); - expect(isErrorTypeFunc(error as SdkError)).toBe(errorTypeResult); -}; - -describe("isRetryableByTrait", () => { - it("should declare error with $retryable set to be a Retryable by trait", () => { - const $retryable = {}; - checkForErrorType(isRetryableByTrait, { $retryable }, true); - }); - - it("should not declare error with $retryable not set to be a Retryable by trait", () => { - checkForErrorType(isRetryableByTrait, {}, false); - }); -}); - -describe("isClockSkewError", () => { - CLOCK_SKEW_ERROR_CODES.forEach((name) => { - it(`should declare error with the name "${name}" to be a ClockSkew error`, () => { - checkForErrorType(isClockSkewError, { name }, true); - }); - }); - - while (true) { - const name = Math.random().toString(36).substring(2); - if (!CLOCK_SKEW_ERROR_CODES.includes(name)) { - it(`should not declare error with the name "${name}" to be a ClockSkew error`, () => { - checkForErrorType(isClockSkewError, { name }, false); - }); - break; - } - } -}); - -describe("isThrottlingError", () => { - [429].forEach((httpStatusCode) => { - it(`should declare error with the HTTP Status Code "${httpStatusCode}" to be a Throttling error`, () => { - checkForErrorType(isTransientError, { httpStatusCode }, false); - }); - }); - - THROTTLING_ERROR_CODES.forEach((name) => { - it(`should declare error with the name "${name}" to be a Throttling error`, () => { - checkForErrorType(isThrottlingError, { name }, true); - }); - }); - - while (true) { - const name = Math.random().toString(36).substring(2); - if (!THROTTLING_ERROR_CODES.includes(name)) { - it(`should not declare error with the name "${name}" to be a Throttling error`, () => { - checkForErrorType(isThrottlingError, { name }, false); - }); - break; - } - } - - it("should declare error with $retryable.throttling set to true to be a Throttling error", () => { - const $retryable = { throttling: true }; - checkForErrorType(isThrottlingError, { $retryable }, true); - }); - - it("should not declare error with $retryable.throttling set to false to be a Throttling error", () => { - const $retryable = { throttling: false }; - checkForErrorType(isThrottlingError, { $retryable }, false); - }); - - it("should not declare error with $retryable.throttling not set to be a Throttling error", () => { - const $retryable = {}; - checkForErrorType(isThrottlingError, { $retryable }, false); - }); -}); - -describe("isTransientError", () => { - TRANSIENT_ERROR_CODES.forEach((name) => { - it(`should declare error with the name "${name}" to be a Transient error`, () => { - checkForErrorType(isTransientError, { name }, true); - }); - }); - - TRANSIENT_ERROR_STATUS_CODES.forEach((httpStatusCode) => { - it(`should declare error with the HTTP Status Code "${httpStatusCode}" to be a Transient error`, () => { - checkForErrorType(isTransientError, { httpStatusCode }, true); - }); - }); - - while (true) { - const name = Math.random().toString(36).substring(2); - if (!TRANSIENT_ERROR_CODES.includes(name)) { - it(`should not declare error with the name "${name}" to be a Transient error`, () => { - checkForErrorType(isTransientError, { name }, false); - }); - break; - } - } - - while (true) { - const httpStatusCode = Math.ceil(Math.random() * 10 ** 3); - if (!TRANSIENT_ERROR_STATUS_CODES.includes(httpStatusCode)) { - it(`should declare error with the HTTP Status Code "${httpStatusCode}" to be a Transient error`, () => { - checkForErrorType(isTransientError, { httpStatusCode }, false); - }); - break; - } - } -}); - -describe("isServerError", () => { - [501, 505, 511].forEach((httpStatusCode) => { - it(`should declare error with the HTTP Status Code "${httpStatusCode}" to be a Server error`, () => { - checkForErrorType(isServerError, { httpStatusCode }, true); - }); - }); - TRANSIENT_ERROR_STATUS_CODES.forEach((httpStatusCode) => { - it(`should declare error with the HTTP Status Code "${httpStatusCode}" to not be be a Server error`, () => { - checkForErrorType(isServerError, { httpStatusCode }, false); - }); - }); -}); diff --git a/packages/service-error-classification/src/index.ts b/packages/service-error-classification/src/index.ts index 752307953d3c..f813b73e6794 100644 --- a/packages/service-error-classification/src/index.ts +++ b/packages/service-error-classification/src/index.ts @@ -1,40 +1 @@ -import { SdkError } from "@aws-sdk/types"; - -import { - CLOCK_SKEW_ERROR_CODES, - NODEJS_TIMEOUT_ERROR_CODES, - THROTTLING_ERROR_CODES, - TRANSIENT_ERROR_CODES, - TRANSIENT_ERROR_STATUS_CODES, -} from "./constants"; - -export const isRetryableByTrait = (error: SdkError) => error.$retryable !== undefined; - -export const isClockSkewError = (error: SdkError) => CLOCK_SKEW_ERROR_CODES.includes(error.name); - -export const isThrottlingError = (error: SdkError) => - error.$metadata?.httpStatusCode === 429 || - THROTTLING_ERROR_CODES.includes(error.name) || - error.$retryable?.throttling == true; - -/** - * Though NODEJS_TIMEOUT_ERROR_CODES are platform specific, they are - * included here because there is an error scenario with unknown root - * cause where the NodeHttpHandler does not decorate the Error with - * the name "TimeoutError" to be checked by the TRANSIENT_ERROR_CODES condition. - */ -export const isTransientError = (error: SdkError) => - TRANSIENT_ERROR_CODES.includes(error.name) || - NODEJS_TIMEOUT_ERROR_CODES.includes((error as { code?: string })?.code || "") || - TRANSIENT_ERROR_STATUS_CODES.includes(error.$metadata?.httpStatusCode || 0); - -export const isServerError = (error: SdkError) => { - if (error.$metadata?.httpStatusCode !== undefined) { - const statusCode = error.$metadata.httpStatusCode; - if (500 <= statusCode && statusCode <= 599 && !isTransientError(error)) { - return true; - } - return false; - } - return false; -}; +export * from "@smithy/service-error-classification"; diff --git a/packages/shared-ini-file-loader/jest.config.js b/packages/shared-ini-file-loader/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/shared-ini-file-loader/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/shared-ini-file-loader/package.json b/packages/shared-ini-file-loader/package.json index 9ab9b58b42e1..2fbd48acb1e4 100644 --- a/packages/shared-ini-file-loader/package.json +++ b/packages/shared-ini-file-loader/package.json @@ -2,7 +2,7 @@ "name": "@aws-sdk/shared-ini-file-loader", "version": "3.370.0", "dependencies": { - "@aws-sdk/types": "*", + "@smithy/shared-ini-file-loader": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { @@ -23,7 +23,7 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "extract:docs": "api-extractor run --local", - "test": "jest" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", diff --git a/packages/shared-ini-file-loader/src/getConfigFilepath.spec.ts b/packages/shared-ini-file-loader/src/getConfigFilepath.spec.ts deleted file mode 100644 index 5a9964db3661..000000000000 --- a/packages/shared-ini-file-loader/src/getConfigFilepath.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { join } from "path"; - -import { ENV_CONFIG_PATH, getConfigFilepath } from "./getConfigFilepath"; -import { getHomeDir } from "./getHomeDir"; - -jest.mock("path"); -jest.mock("./getHomeDir"); - -describe(getConfigFilepath.name, () => { - const mockSeparator = "/"; - const mockHomeDir = "/mock/home/dir"; - - const mockConfigFilepath = "/mock/file/path/config"; - const defaultConfigFilepath = `${mockHomeDir}/.aws/config`; - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("returns configFilePath from default locations", () => { - (join as jest.Mock).mockImplementation((...args) => args.join(mockSeparator)); - (getHomeDir as jest.Mock).mockReturnValue(mockHomeDir); - expect(getConfigFilepath()).toStrictEqual(defaultConfigFilepath); - expect(getHomeDir).toHaveBeenCalledWith(); - expect(join).toHaveBeenCalledWith(mockHomeDir, ".aws", "config"); - }); - - it("returns configFile from location defined in environment", () => { - const OLD_ENV = process.env; - process.env = { - ...OLD_ENV, - [ENV_CONFIG_PATH]: mockConfigFilepath, - }; - expect(getConfigFilepath()).toStrictEqual(mockConfigFilepath); - expect(getHomeDir).not.toHaveBeenCalled(); - expect(join).not.toHaveBeenCalled(); - process.env = OLD_ENV; - }); -}); diff --git a/packages/shared-ini-file-loader/src/getConfigFilepath.ts b/packages/shared-ini-file-loader/src/getConfigFilepath.ts deleted file mode 100644 index ba684e482aa0..000000000000 --- a/packages/shared-ini-file-loader/src/getConfigFilepath.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { join } from "path"; - -import { getHomeDir } from "./getHomeDir"; - -export const ENV_CONFIG_PATH = "AWS_CONFIG_FILE"; - -export const getConfigFilepath = () => process.env[ENV_CONFIG_PATH] || join(getHomeDir(), ".aws", "config"); diff --git a/packages/shared-ini-file-loader/src/getCredentialsFilepath.spec.ts b/packages/shared-ini-file-loader/src/getCredentialsFilepath.spec.ts deleted file mode 100644 index 822a89148612..000000000000 --- a/packages/shared-ini-file-loader/src/getCredentialsFilepath.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { join } from "path"; - -import { ENV_CREDENTIALS_PATH, getCredentialsFilepath } from "./getCredentialsFilepath"; -import { getHomeDir } from "./getHomeDir"; - -jest.mock("path"); -jest.mock("./getHomeDir"); - -describe(getCredentialsFilepath.name, () => { - const mockSeparator = "/"; - const mockHomeDir = "/mock/home/dir"; - - const mockConfigFilepath = "/mock/file/path/credentials"; - const defaultConfigFilepath = `${mockHomeDir}/.aws/credentials`; - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("returns configFilePath from default locations", () => { - (join as jest.Mock).mockImplementation((...args) => args.join(mockSeparator)); - (getHomeDir as jest.Mock).mockReturnValue(mockHomeDir); - expect(getCredentialsFilepath()).toStrictEqual(defaultConfigFilepath); - expect(getHomeDir).toHaveBeenCalledWith(); - expect(join).toHaveBeenCalledWith(mockHomeDir, ".aws", "credentials"); - }); - - it("returns configFile from location defined in environment", () => { - const OLD_ENV = process.env; - process.env = { - ...OLD_ENV, - [ENV_CREDENTIALS_PATH]: mockConfigFilepath, - }; - expect(getCredentialsFilepath()).toStrictEqual(mockConfigFilepath); - expect(getHomeDir).not.toHaveBeenCalled(); - expect(join).not.toHaveBeenCalled(); - process.env = OLD_ENV; - }); -}); diff --git a/packages/shared-ini-file-loader/src/getCredentialsFilepath.ts b/packages/shared-ini-file-loader/src/getCredentialsFilepath.ts deleted file mode 100644 index d367cd985c4d..000000000000 --- a/packages/shared-ini-file-loader/src/getCredentialsFilepath.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { join } from "path"; - -import { getHomeDir } from "./getHomeDir"; - -export const ENV_CREDENTIALS_PATH = "AWS_SHARED_CREDENTIALS_FILE"; - -export const getCredentialsFilepath = () => - process.env[ENV_CREDENTIALS_PATH] || join(getHomeDir(), ".aws", "credentials"); diff --git a/packages/shared-ini-file-loader/src/getHomeDir.spec.ts b/packages/shared-ini-file-loader/src/getHomeDir.spec.ts deleted file mode 100644 index c3aa7d5d3326..000000000000 --- a/packages/shared-ini-file-loader/src/getHomeDir.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { homedir } from "os"; -import { sep } from "path"; - -import { getHomeDir } from "./getHomeDir"; - -jest.mock("os"); - -describe(getHomeDir.name, () => { - const mockHOME = "mockHOME"; - const mockUSERPROFILE = "mockUSERPROFILE"; - const mockHOMEPATH = "mockHOMEPATH"; - const mockHOMEDRIVE = "mockHOMEDRIVE"; - const mockHomeDir = "mockHomeDir"; - - const OLD_ENV = process.env; - - beforeEach(() => { - (homedir as jest.Mock).mockReturnValue(mockHomeDir); - process.env = { - ...OLD_ENV, - HOME: mockHOME, - USERPROFILE: mockUSERPROFILE, - HOMEPATH: mockHOMEPATH, - HOMEDRIVE: mockHOMEDRIVE, - }; - }); - - afterEach(() => { - process.env = OLD_ENV; - jest.clearAllMocks(); - jest.resetModules(); - }); - - it("returns value in process.env.HOME first", () => { - expect(getHomeDir()).toEqual(mockHOME); - }); - - it("returns value in process.env.USERPROFILE second", () => { - process.env = { ...process.env, HOME: undefined }; - expect(getHomeDir()).toEqual(mockUSERPROFILE); - }); - - describe("returns value in HOMEPATH third", () => { - beforeEach(() => { - process.env = { ...process.env, HOME: undefined, USERPROFILE: undefined }; - }); - - it("uses value in process.env.HOMEDRIVE if it's set", () => { - expect(getHomeDir()).toEqual(`${mockHOMEDRIVE}${mockHOMEPATH}`); - }); - - it("uses default if process.env.HOMEDRIVE is not set", () => { - process.env = { ...process.env, HOMEDRIVE: undefined }; - expect(getHomeDir()).toEqual(`C:${sep}${mockHOMEPATH}`); - }); - }); - - it("returns value from homedir fourth", () => { - process.env = { ...process.env, HOME: undefined, USERPROFILE: undefined, HOMEPATH: undefined }; - expect(getHomeDir()).toEqual(mockHomeDir); - }); -}); diff --git a/packages/shared-ini-file-loader/src/getHomeDir.ts b/packages/shared-ini-file-loader/src/getHomeDir.ts deleted file mode 100644 index f828bf35ce2a..000000000000 --- a/packages/shared-ini-file-loader/src/getHomeDir.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { homedir } from "os"; -import { sep } from "path"; - -/** - * Get the HOME directory for the current runtime. - * - * @internal - */ -export const getHomeDir = (): string => { - const { HOME, USERPROFILE, HOMEPATH, HOMEDRIVE = `C:${sep}` } = process.env; - - if (HOME) return HOME; - if (USERPROFILE) return USERPROFILE; - if (HOMEPATH) return `${HOMEDRIVE}${HOMEPATH}`; - - return homedir(); -}; diff --git a/packages/shared-ini-file-loader/src/getProfileData.spec.ts b/packages/shared-ini-file-loader/src/getProfileData.spec.ts deleted file mode 100644 index d8e84a185262..000000000000 --- a/packages/shared-ini-file-loader/src/getProfileData.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { getProfileData } from "./getProfileData"; - -describe(getProfileData.name, () => { - it("returns empty for no data", () => { - expect(getProfileData({})).toStrictEqual({}); - }); - - it("returns default profile if present", () => { - const mockInput = { default: { key: "value" } }; - expect(getProfileData(mockInput)).toStrictEqual(mockInput); - }); - - it("skips profiles without prefix profile", () => { - const mockInput = { test: { key: "value" } }; - expect(getProfileData(mockInput)).toStrictEqual({}); - }); - - it("skips profiles with different prefix", () => { - const mockInput = { "not-profile test": { key: "value" } }; - expect(getProfileData(mockInput)).toStrictEqual({}); - }); - - describe("normalizes profile names", () => { - const getMockProfileData = (profileName: string) => - [1, 2, 3] - .map((num) => [`key_${profileName}_${num}`, `value_${profileName}_${num}`]) - .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); - - const getMockOutput = (profileNames: string[]) => - profileNames.reduce((acc, profileName) => ({ ...acc, [profileName]: getMockProfileData(profileName) }), {}); - - const getMockInput = (mockOutput: Record>) => - Object.entries(mockOutput).reduce((acc, [key, value]) => ({ ...acc, [`profile ${key}`]: value }), {}); - - it("single profile", () => { - const mockOutput = getMockOutput(["one"]); - const mockInput = getMockInput(mockOutput); - expect(getProfileData(mockInput)).toStrictEqual(mockOutput); - }); - - it("two profiles", () => { - const mockOutput = getMockOutput(["one", "two"]); - const mockInput = getMockInput(mockOutput); - expect(getProfileData(mockInput)).toStrictEqual(mockOutput); - }); - - it("three profiles", () => { - const mockOutput = getMockOutput(["one", "two", "three"]); - const mockInput = getMockInput(mockOutput); - expect(getProfileData(mockInput)).toStrictEqual(mockOutput); - }); - - it("with default", () => { - const defaultInput = { default: { key: "value" } }; - const mockOutput = getMockOutput(["one"]); - const mockInput = getMockInput(mockOutput); - expect(getProfileData({ ...defaultInput, ...mockInput })).toStrictEqual({ ...defaultInput, ...mockOutput }); - }); - - it("with profileName without prefix", () => { - const profileWithPrefix = { test: { key: "value" } }; - const mockOutput = getMockOutput(["one"]); - const mockInput = getMockInput(mockOutput); - expect(getProfileData({ ...profileWithPrefix, ...mockInput })).toStrictEqual(mockOutput); - }); - }); -}); diff --git a/packages/shared-ini-file-loader/src/getProfileData.ts b/packages/shared-ini-file-loader/src/getProfileData.ts deleted file mode 100644 index 95e214ab6f59..000000000000 --- a/packages/shared-ini-file-loader/src/getProfileData.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ParsedIniData } from "@aws-sdk/types"; - -const profileKeyRegex = /^profile\s(["'])?([^\1]+)\1$/; - -/** - * Returns the profile data from parsed ini data. - * * Returns data for `default` - * * Reads profileName after profile prefix including/excluding quotes - */ -export const getProfileData = (data: ParsedIniData): ParsedIniData => - Object.entries(data) - // filter out non-profile keys - .filter(([key]) => profileKeyRegex.test(key)) - // replace profile key with profile name - .reduce((acc, [key, value]) => ({ ...acc, [profileKeyRegex.exec(key)![2]]: value }), { - // Populate default profile, if present. - ...(data.default && { default: data.default }), - }); diff --git a/packages/shared-ini-file-loader/src/getProfileName.spec.ts b/packages/shared-ini-file-loader/src/getProfileName.spec.ts deleted file mode 100644 index 9780c3519e11..000000000000 --- a/packages/shared-ini-file-loader/src/getProfileName.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { DEFAULT_PROFILE, ENV_PROFILE, getProfileName } from "./getProfileName"; - -describe(getProfileName.name, () => { - const OLD_ENV = process.env; - const mockProfileNameFromEnv = "mockProfileNameFromEnv"; - - beforeEach(() => { - process.env = { - ...OLD_ENV, - [ENV_PROFILE]: mockProfileNameFromEnv, - }; - }); - - afterEach(() => { - process.env = OLD_ENV; - }); - - it("returns profile if present in param", () => { - const profile = "mockProfile"; - expect(getProfileName({ profile })).toBe(profile); - }); - - it(`returns profile from env var '${ENV_PROFILE}' if present`, () => { - expect(getProfileName({})).toBe(mockProfileNameFromEnv); - }); - - it(`returns profile '${DEFAULT_PROFILE}' as default`, () => { - process.env[ENV_PROFILE] = undefined; - expect(getProfileName({})).toBe(DEFAULT_PROFILE); - }); -}); diff --git a/packages/shared-ini-file-loader/src/getProfileName.ts b/packages/shared-ini-file-loader/src/getProfileName.ts deleted file mode 100644 index 0844812c77a4..000000000000 --- a/packages/shared-ini-file-loader/src/getProfileName.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const ENV_PROFILE = "AWS_PROFILE"; -export const DEFAULT_PROFILE = "default"; - -export const getProfileName = (init: { profile?: string }): string => - init.profile || process.env[ENV_PROFILE] || DEFAULT_PROFILE; diff --git a/packages/shared-ini-file-loader/src/getSSOTokenFilepath.spec.ts b/packages/shared-ini-file-loader/src/getSSOTokenFilepath.spec.ts deleted file mode 100644 index 3edff44fe410..000000000000 --- a/packages/shared-ini-file-loader/src/getSSOTokenFilepath.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { createHash } from "crypto"; -import { join } from "path"; - -import { getHomeDir } from "./getHomeDir"; -import { getSSOTokenFilepath } from "./getSSOTokenFilepath"; - -jest.mock("crypto"); -jest.mock("./getHomeDir"); - -describe(getSSOTokenFilepath.name, () => { - const mockCacheName = "mockCacheName"; - const mockDigest = jest.fn().mockReturnValue(mockCacheName); - const mockUpdate = jest.fn().mockReturnValue({ digest: mockDigest }); - const mockHomeDir = "/home/dir"; - const mockSsoStartUrl = "mock_sso_start_url"; - - beforeEach(() => { - (createHash as jest.Mock).mockReturnValue({ update: mockUpdate }); - (getHomeDir as jest.Mock).mockReturnValue(mockHomeDir); - }); - - afterEach(() => { - expect(createHash).toHaveBeenCalledWith("sha1"); - jest.clearAllMocks(); - }); - - describe("re-throws error", () => { - const mockError = new Error("error"); - - it("when createHash throws error", () => { - (createHash as jest.Mock).mockImplementationOnce(() => { - throw mockError; - }); - expect(() => getSSOTokenFilepath(mockSsoStartUrl)).toThrow(mockError); - expect(mockUpdate).not.toHaveBeenCalled(); - expect(mockDigest).not.toHaveBeenCalled(); - expect(getHomeDir).not.toHaveBeenCalled(); - }); - - it("when hash.update() throws error", () => { - mockUpdate.mockImplementationOnce(() => { - throw mockError; - }); - expect(() => getSSOTokenFilepath(mockSsoStartUrl)).toThrow(mockError); - expect(mockUpdate).toHaveBeenCalledWith(mockSsoStartUrl); - expect(mockDigest).not.toHaveBeenCalled(); - expect(getHomeDir).not.toHaveBeenCalled(); - }); - - it("when hash.digest() throws error", () => { - mockDigest.mockImplementationOnce(() => { - throw mockError; - }); - expect(() => getSSOTokenFilepath(mockSsoStartUrl)).toThrow(mockError); - expect(mockUpdate).toHaveBeenCalledWith(mockSsoStartUrl); - expect(mockDigest).toHaveBeenCalledWith("hex"); - expect(getHomeDir).not.toHaveBeenCalled(); - }); - - it("when getHomeDir() throws error", () => { - (getHomeDir as jest.Mock).mockImplementationOnce(() => { - throw mockError; - }); - expect(() => getSSOTokenFilepath(mockSsoStartUrl)).toThrow(mockError); - expect(mockUpdate).toHaveBeenCalledWith(mockSsoStartUrl); - expect(mockDigest).toHaveBeenCalledWith("hex"); - expect(getHomeDir).toHaveBeenCalled(); - }); - }); - - it("returns token filepath", () => { - const ssoTokenFilepath = getSSOTokenFilepath(mockSsoStartUrl); - expect(ssoTokenFilepath).toStrictEqual(join(mockHomeDir, ".aws", "sso", "cache", `${mockCacheName}.json`)); - expect(mockUpdate).toHaveBeenCalledWith(mockSsoStartUrl); - expect(mockDigest).toHaveBeenCalledWith("hex"); - expect(getHomeDir).toHaveBeenCalled(); - }); -}); diff --git a/packages/shared-ini-file-loader/src/getSSOTokenFilepath.ts b/packages/shared-ini-file-loader/src/getSSOTokenFilepath.ts deleted file mode 100644 index 8b92c9e6dc80..000000000000 --- a/packages/shared-ini-file-loader/src/getSSOTokenFilepath.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createHash } from "crypto"; -import { join } from "path"; - -import { getHomeDir } from "./getHomeDir"; - -/** - * Returns the filepath of the file where SSO token is stored. - */ -export const getSSOTokenFilepath = (id: string) => { - const hasher = createHash("sha1"); - const cacheName = hasher.update(id).digest("hex"); - return join(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`); -}; diff --git a/packages/shared-ini-file-loader/src/getSSOTokenFromFile.spec.ts b/packages/shared-ini-file-loader/src/getSSOTokenFromFile.spec.ts deleted file mode 100644 index c6cb3cac0cc1..000000000000 --- a/packages/shared-ini-file-loader/src/getSSOTokenFromFile.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -// ToDo: Change to "fs/promises" when supporting nodejs>=14 -import { promises } from "fs"; - -import { getSSOTokenFilepath } from "./getSSOTokenFilepath"; -import { getSSOTokenFromFile } from "./getSSOTokenFromFile"; - -jest.mock("fs", () => ({ promises: { readFile: jest.fn() } })); -jest.mock("./getSSOTokenFilepath"); - -describe(getSSOTokenFromFile.name, () => { - const mockSsoStartUrl = "mock_sso_start_url"; - const mockSsoTokenFilepath = "/home/dir/.aws/sso/cache/mockCacheName.json"; - - const mockToken = { - accessToken: "mockAccessToken", - expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), - }; - - beforeEach(() => { - (getSSOTokenFilepath as jest.Mock).mockReturnValue(mockSsoTokenFilepath); - (promises.readFile as jest.Mock).mockResolvedValue(JSON.stringify(mockToken)); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("re-throws if getting SSO Token filepath fails", async () => { - const expectedError = new Error("error"); - (getSSOTokenFilepath as jest.Mock).mockImplementationOnce(() => { - throw expectedError; - }); - - try { - await getSSOTokenFromFile(mockSsoStartUrl); - fail(`expected ${expectedError}`); - } catch (error) { - expect(error).toStrictEqual(expectedError); - } - expect(promises.readFile).not.toHaveBeenCalled(); - }); - - it("re-throws if readFile fails", async () => { - const expectedError = new Error("error"); - (promises.readFile as jest.Mock).mockRejectedValue(expectedError); - - try { - await getSSOTokenFromFile(mockSsoStartUrl); - fail(`expected ${expectedError}`); - } catch (error) { - expect(error).toStrictEqual(expectedError); - } - expect(promises.readFile).toHaveBeenCalledWith(mockSsoTokenFilepath, "utf8"); - }); - - it("re-throws if token is not a valid JSON", async () => { - const errMsg = "Unexpected token"; - (promises.readFile as jest.Mock).mockReturnValue("invalid JSON"); - - try { - await getSSOTokenFromFile(mockSsoStartUrl); - fail(`expected '${errMsg}'`); - } catch (error) { - expect(error.message).toContain(errMsg); - } - expect(promises.readFile).toHaveBeenCalledWith(mockSsoTokenFilepath, "utf8"); - }); - - it("returns token when it's valid", async () => { - const token = await getSSOTokenFromFile(mockSsoStartUrl); - expect(token).toStrictEqual(mockToken); - expect(promises.readFile).toHaveBeenCalledWith(mockSsoTokenFilepath, "utf8"); - }); -}); diff --git a/packages/shared-ini-file-loader/src/getSSOTokenFromFile.ts b/packages/shared-ini-file-loader/src/getSSOTokenFromFile.ts deleted file mode 100644 index a7b3ba1fedb7..000000000000 --- a/packages/shared-ini-file-loader/src/getSSOTokenFromFile.ts +++ /dev/null @@ -1,63 +0,0 @@ -// ToDo: Change to "fs/promises" when supporting nodejs>=14 -import { promises as fsPromises } from "fs"; - -import { getSSOTokenFilepath } from "./getSSOTokenFilepath"; - -const { readFile } = fsPromises; - -/** - * Cached SSO token retrieved from SSO login flow. - */ -export interface SSOToken { - /** - * A base64 encoded string returned by the sso-oidc service. - */ - accessToken: string; - - /** - * The expiration time of the accessToken as an RFC 3339 formatted timestamp. - */ - expiresAt: string; - - /** - * The token used to obtain an access token in the event that the accessToken is invalid or expired. - */ - refreshToken?: string; - - /** - * The unique identifier string for each client. The client ID generated when performing the registration - * portion of the OIDC authorization flow. This is used to refresh the accessToken. - */ - clientId?: string; - - /** - * A secret string generated when performing the registration portion of the OIDC authorization flow. - * This is used to refresh the accessToken. - */ - clientSecret?: string; - - /** - * The expiration time of the client registration (clientId and clientSecret) as an RFC 3339 formatted timestamp. - */ - registrationExpiresAt?: string; - - /** - * The configured sso_region for the profile that credentials are being resolved for. - */ - region?: string; - - /** - * The configured sso_start_url for the profile that credentials are being resolved for. - */ - startUrl?: string; -} - -/** - * @param id - can be either a start URL or the SSO session name. - * Returns the SSO token from the file system. - */ -export const getSSOTokenFromFile = async (id: string) => { - const ssoTokenFilepath = getSSOTokenFilepath(id); - const ssoTokenText = await readFile(ssoTokenFilepath, "utf8"); - return JSON.parse(ssoTokenText) as SSOToken; -}; diff --git a/packages/shared-ini-file-loader/src/getSsoSessionData.spec.ts b/packages/shared-ini-file-loader/src/getSsoSessionData.spec.ts deleted file mode 100644 index bf8b0ca0e57c..000000000000 --- a/packages/shared-ini-file-loader/src/getSsoSessionData.spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { getSsoSessionData } from "./getSsoSessionData"; - -describe(getSsoSessionData.name, () => { - it("returns empty for no data", () => { - expect(getSsoSessionData({})).toStrictEqual({}); - }); - - it("skips sections without prefix sso-session", () => { - const mockInput = { test: { key: "value" } }; - expect(getSsoSessionData(mockInput)).toStrictEqual({}); - }); - - it("skips sections with different prefix", () => { - const mockInput = { "not-sso-session test": { key: "value" } }; - expect(getSsoSessionData(mockInput)).toStrictEqual({}); - }); - - describe("normalizes sso-session names", () => { - const getMockSsoSessionData = (ssoSessionName: string) => - [1, 2, 3] - .map((num) => [`key_${ssoSessionName}_${num}`, `value_${ssoSessionName}_${num}`]) - .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); - - const getMockOutput = (ssoSessionNames: string[]) => - ssoSessionNames.reduce((acc, profileName) => ({ ...acc, [profileName]: getMockSsoSessionData(profileName) }), {}); - - const getMockInput = (mockOutput: { [key: string]: { [key: string]: string } }) => - Object.entries(mockOutput).reduce((acc, [key, value]) => ({ ...acc, [`sso-session ${key}`]: value }), {}); - - it("single sso-session section", () => { - const mockOutput = getMockOutput(["one"]); - const mockInput = getMockInput(mockOutput); - expect(getSsoSessionData(mockInput)).toStrictEqual(mockOutput); - }); - - it("two sso-session sections", () => { - const mockOutput = getMockOutput(["one", "two"]); - const mockInput = getMockInput(mockOutput); - expect(getSsoSessionData(mockInput)).toStrictEqual(mockOutput); - }); - - it("three sso-session sections", () => { - const mockOutput = getMockOutput(["one", "two", "three"]); - const mockInput = getMockInput(mockOutput); - expect(getSsoSessionData(mockInput)).toStrictEqual(mockOutput); - }); - - it("with section without prefix", () => { - const sectionWithoutPrefix = { test: { key: "value" } }; - const mockOutput = getMockOutput(["one"]); - const mockInput = getMockInput(mockOutput); - expect(getSsoSessionData({ ...sectionWithoutPrefix, ...mockInput })).toStrictEqual(mockOutput); - }); - }); -}); diff --git a/packages/shared-ini-file-loader/src/getSsoSessionData.ts b/packages/shared-ini-file-loader/src/getSsoSessionData.ts deleted file mode 100644 index 187c4f13a43f..000000000000 --- a/packages/shared-ini-file-loader/src/getSsoSessionData.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ParsedIniData } from "@aws-sdk/types"; - -const ssoSessionKeyRegex = /^sso-session\s(["'])?([^\1]+)\1$/; - -/** - * Returns the sso-session data from parsed ini data by reading - * ssoSessionName after sso-session prefix including/excluding quotes - */ -export const getSsoSessionData = (data: ParsedIniData): ParsedIniData => - Object.entries(data) - // filter out non sso-session keys - .filter(([key]) => ssoSessionKeyRegex.test(key)) - // replace sso-session key with sso-session name - .reduce((acc, [key, value]) => ({ ...acc, [ssoSessionKeyRegex.exec(key)![2]]: value }), {}); diff --git a/packages/shared-ini-file-loader/src/index.ts b/packages/shared-ini-file-loader/src/index.ts index 3e8b2c7470dd..c9afe25d7d57 100644 --- a/packages/shared-ini-file-loader/src/index.ts +++ b/packages/shared-ini-file-loader/src/index.ts @@ -1,8 +1 @@ -export * from "./getHomeDir"; -export * from "./getProfileName"; -export * from "./getSSOTokenFilepath"; -export * from "./getSSOTokenFromFile"; -export * from "./loadSharedConfigFiles"; -export * from "./loadSsoSessionData"; -export * from "./parseKnownFiles"; -export * from "./types"; +export * from "@smithy/shared-ini-file-loader"; diff --git a/packages/shared-ini-file-loader/src/loadSharedConfigFiles.spec.ts b/packages/shared-ini-file-loader/src/loadSharedConfigFiles.spec.ts deleted file mode 100644 index bc3bc16335c3..000000000000 --- a/packages/shared-ini-file-loader/src/loadSharedConfigFiles.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { getConfigFilepath } from "./getConfigFilepath"; -import { getCredentialsFilepath } from "./getCredentialsFilepath"; -import { getProfileData } from "./getProfileData"; -import { loadSharedConfigFiles } from "./loadSharedConfigFiles"; -import { parseIni } from "./parseIni"; -import { slurpFile } from "./slurpFile"; - -jest.mock("./getConfigFilepath"); -jest.mock("./getCredentialsFilepath"); -jest.mock("./getProfileData"); -jest.mock("./parseIni"); -jest.mock("./slurpFile"); - -describe("loadSharedConfigFiles", () => { - const mockConfigFilepath = "/mock/file/path/config"; - const mockCredsFilepath = "/mock/file/path/credentials"; - const mockSharedConfigFiles = { - configFile: mockConfigFilepath, - credentialsFile: mockCredsFilepath, - }; - - beforeEach(() => { - (getConfigFilepath as jest.Mock).mockReturnValue(mockConfigFilepath); - (getCredentialsFilepath as jest.Mock).mockReturnValue(mockCredsFilepath); - (parseIni as jest.Mock).mockImplementation((args) => args); - (getProfileData as jest.Mock).mockImplementation((args) => args); - (slurpFile as jest.Mock).mockImplementation((path) => Promise.resolve(path)); - }); - - afterEach(() => { - jest.clearAllMocks(); - jest.resetModules(); - }); - - it("returns configFile and credentialsFile from default locations", async () => { - const sharedConfigFiles = await loadSharedConfigFiles(); - expect(sharedConfigFiles).toStrictEqual(mockSharedConfigFiles); - expect(getConfigFilepath).toHaveBeenCalledWith(); - expect(getCredentialsFilepath).toHaveBeenCalledWith(); - }); - - it("returns configFile and credentialsFile from init if defined", async () => { - const sharedConfigFiles = await loadSharedConfigFiles({ - filepath: mockCredsFilepath, - configFilepath: mockConfigFilepath, - }); - expect(sharedConfigFiles).toStrictEqual(mockSharedConfigFiles); - 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"); - const sharedConfigFiles = await loadSharedConfigFiles(); - expect(sharedConfigFiles).toStrictEqual({ configFile: {}, credentialsFile: {} }); - }); - - it("when parseIni throws error", async () => { - (parseIni as jest.Mock).mockRejectedValue("error"); - const sharedConfigFiles = await loadSharedConfigFiles(); - expect(sharedConfigFiles).toStrictEqual({ configFile: {}, credentialsFile: {} }); - }); - - it("when normalizeConfigFile throws error", async () => { - (getProfileData as jest.Mock).mockRejectedValue("error"); - const sharedConfigFiles = await loadSharedConfigFiles(); - expect(sharedConfigFiles).toStrictEqual({ - configFile: {}, - credentialsFile: mockCredsFilepath, - }); - }); - }); -}); diff --git a/packages/shared-ini-file-loader/src/loadSharedConfigFiles.ts b/packages/shared-ini-file-loader/src/loadSharedConfigFiles.ts deleted file mode 100644 index 63929575ea1d..000000000000 --- a/packages/shared-ini-file-loader/src/loadSharedConfigFiles.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { SharedConfigFiles } from "@aws-sdk/types"; - -import { getConfigFilepath } from "./getConfigFilepath"; -import { getCredentialsFilepath } from "./getCredentialsFilepath"; -import { getProfileData } from "./getProfileData"; -import { parseIni } from "./parseIni"; -import { slurpFile } from "./slurpFile"; - -export interface SharedConfigInit { - /** - * The path at which to locate the ini credentials file. Defaults to the - * value of the `AWS_SHARED_CREDENTIALS_FILE` environment variable (if - * defined) or `~/.aws/credentials` otherwise. - */ - filepath?: string; - - /** - * The path at which to locate the ini config file. Defaults to the value of - * the `AWS_CONFIG_FILE` environment variable (if defined) or - * `~/.aws/config` otherwise. - */ - configFilepath?: string; - - /** - * Configuration files are normally cached after the first time they are loaded. When this - * property is set, the provider will always reload any configuration files loaded before. - */ - ignoreCache?: boolean; -} - -const swallowError = () => ({}); - -export const loadSharedConfigFiles = async (init: SharedConfigInit = {}): Promise => { - const { filepath = getCredentialsFilepath(), configFilepath = getConfigFilepath() } = init; - - const parsedFiles = await Promise.all([ - slurpFile(configFilepath, { - ignoreCache: init.ignoreCache, - }) - .then(parseIni) - .then(getProfileData) - .catch(swallowError), - slurpFile(filepath, { - ignoreCache: init.ignoreCache, - }) - .then(parseIni) - .catch(swallowError), - ]); - - return { - configFile: parsedFiles[0], - credentialsFile: parsedFiles[1], - }; -}; diff --git a/packages/shared-ini-file-loader/src/loadSsoSessionData.spec.ts b/packages/shared-ini-file-loader/src/loadSsoSessionData.spec.ts deleted file mode 100644 index ea2f9e549e44..000000000000 --- a/packages/shared-ini-file-loader/src/loadSsoSessionData.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { getConfigFilepath } from "./getConfigFilepath"; -import { getSsoSessionData } from "./getSsoSessionData"; -import { loadSsoSessionData } from "./loadSsoSessionData"; -import { parseIni } from "./parseIni"; -import { slurpFile } from "./slurpFile"; - -jest.mock("./getConfigFilepath"); -jest.mock("./getSsoSessionData"); -jest.mock("./parseIni"); -jest.mock("./slurpFile"); - -describe(loadSsoSessionData.name, () => { - const mockConfigFilepath = "/mock/file/path/config"; - const mockSsoSessionData = { test: { key: "value" } }; - - beforeEach(() => { - (getConfigFilepath as jest.Mock).mockReturnValue(mockConfigFilepath); - (parseIni as jest.Mock).mockImplementation((args) => args); - (getSsoSessionData as jest.Mock).mockReturnValue(mockSsoSessionData); - (slurpFile as jest.Mock).mockImplementation((path) => Promise.resolve(path)); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("returns configFile from default locations", async () => { - const ssoSessionData = await loadSsoSessionData(); - expect(ssoSessionData).toStrictEqual(mockSsoSessionData); - expect(getConfigFilepath).toHaveBeenCalledWith(); - }); - - it("returns configFile from init if defined", async () => { - const ssoSessionData = await loadSsoSessionData({ - configFilepath: mockConfigFilepath, - }); - expect(ssoSessionData).toStrictEqual(mockSsoSessionData); - expect(getConfigFilepath).not.toHaveBeenCalled(); - }); - - describe("swallows error and returns empty configuration", () => { - it("when readFile throws error", async () => { - (slurpFile as jest.Mock).mockRejectedValue("error"); - const ssoSessionData = await loadSsoSessionData(); - expect(ssoSessionData).toStrictEqual({}); - }); - - it("when parseIni throws error", async () => { - (parseIni as jest.Mock).mockRejectedValue("error"); - const ssoSessionData = await loadSsoSessionData(); - expect(ssoSessionData).toStrictEqual({}); - }); - - it("when normalizeConfigFile throws error", async () => { - (getSsoSessionData as jest.Mock).mockRejectedValue("error"); - const ssoSessionData = await loadSsoSessionData(); - expect(ssoSessionData).toStrictEqual({}); - }); - }); -}); diff --git a/packages/shared-ini-file-loader/src/loadSsoSessionData.ts b/packages/shared-ini-file-loader/src/loadSsoSessionData.ts deleted file mode 100644 index 1143e52b9fe6..000000000000 --- a/packages/shared-ini-file-loader/src/loadSsoSessionData.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ParsedIniData } from "@aws-sdk/types"; - -import { getConfigFilepath } from "./getConfigFilepath"; -import { getSsoSessionData } from "./getSsoSessionData"; -import { parseIni } from "./parseIni"; -import { slurpFile } from "./slurpFile"; - -export interface SsoSessionInit { - /** - * The path at which to locate the ini config file. Defaults to the value of - * the `AWS_CONFIG_FILE` environment variable (if defined) or - * `~/.aws/config` otherwise. - */ - configFilepath?: string; -} - -const swallowError = () => ({}); - -export const loadSsoSessionData = async (init: SsoSessionInit = {}): Promise => - slurpFile(init.configFilepath ?? getConfigFilepath()) - .then(parseIni) - .then(getSsoSessionData) - .catch(swallowError); diff --git a/packages/shared-ini-file-loader/src/mergeConfigFiles.spec.ts b/packages/shared-ini-file-loader/src/mergeConfigFiles.spec.ts deleted file mode 100644 index ba7782d2b0d7..000000000000 --- a/packages/shared-ini-file-loader/src/mergeConfigFiles.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { mergeConfigFiles } from "./mergeConfigFiles"; - -describe(mergeConfigFiles.name, () => { - it("merges profiles that are in multiple files", () => { - const mockConfigFile = { - profileName1: { configKey: "configValue1" }, - }; - const mockCredentialsFile = { - profileName1: { credsKey: "configValue1" }, - profileName2: { credsKey: "credsValue1" }, - }; - - expect(mergeConfigFiles(mockConfigFile, mockCredentialsFile)).toMatchInlineSnapshot(` - { - "profileName1": { - "configKey": "configValue1", - "credsKey": "configValue1", - }, - "profileName2": { - "credsKey": "credsValue1", - }, - } - `); - }); -}); diff --git a/packages/shared-ini-file-loader/src/mergeConfigFiles.ts b/packages/shared-ini-file-loader/src/mergeConfigFiles.ts deleted file mode 100644 index 8dc14911a20d..000000000000 --- a/packages/shared-ini-file-loader/src/mergeConfigFiles.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ParsedIniData } from "@aws-sdk/types"; - -/** - * Merge multiple profile config files such that settings each file are kept together - * - * @internal - */ -export const mergeConfigFiles = (...files: ParsedIniData[]): ParsedIniData => { - const merged: ParsedIniData = {}; - for (const file of files) { - for (const [key, values] of Object.entries(file)) { - if (merged[key] !== undefined) { - Object.assign(merged[key], values); - } else { - merged[key] = values; - } - } - } - return merged; -}; diff --git a/packages/shared-ini-file-loader/src/parseIni.spec.ts b/packages/shared-ini-file-loader/src/parseIni.spec.ts deleted file mode 100644 index f81c5663d8a9..000000000000 --- a/packages/shared-ini-file-loader/src/parseIni.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { parseIni } from "./parseIni"; - -describe(parseIni.name, () => { - it.each(["__proto__", "profile __proto__"])("throws error if profile name is '%s'", (deniedProfileName) => { - const initData = `[${deniedProfileName}]\nfoo = not_exist`; - expect(() => { - parseIni(initData); - }).toThrowError(`Found invalid profile name "${deniedProfileName}"`); - }); - - describe("parses config for other keys", () => { - const mockProfileName = "mock_profile_name"; - const mockProfileData = { key: "value" }; - - const getMockProfileData = (profileName: string, profileData: Record) => - `[${profileName}]\n${Object.entries(profileData) - .map(([key, value]) => `${key} = ${value}`) - .join("\n")}\n`; - - it("returns data for one profile", () => { - const mockInput = getMockProfileData(mockProfileName, mockProfileData); - expect(parseIni(mockInput)).toStrictEqual({ - [mockProfileName]: mockProfileData, - }); - }); - - it("returns data for two profiles", () => { - const mockProfile1 = getMockProfileData(mockProfileName, mockProfileData); - - const mockProfileName2 = "mock_profile_name_2"; - const mockProfileData2 = { key2: "value2" }; - const mockProfile2 = getMockProfileData(mockProfileName2, mockProfileData2); - - expect(parseIni(`${mockProfile1}${mockProfile2}`)).toStrictEqual({ - [mockProfileName]: mockProfileData, - [mockProfileName2]: mockProfileData2, - }); - }); - - it("skip section if data is not present", () => { - const mockProfileNameWithoutData = "mock_profile_name_without_data"; - const mockInput = getMockProfileData(mockProfileName, mockProfileData); - expect(parseIni(`${mockInput}[${mockProfileNameWithoutData}]`)).toStrictEqual({ - [mockProfileName]: mockProfileData, - }); - expect(parseIni(`[${mockProfileNameWithoutData}]\n${mockInput}`)).toStrictEqual({ - [mockProfileName]: mockProfileData, - }); - }); - - it("returns data profile name containing multiple words", () => { - const mockProfileNameMultiWords = "foo bar baz"; - const mockInput = getMockProfileData(mockProfileNameMultiWords, mockProfileData); - expect(parseIni(mockInput)).toStrictEqual({ - [mockProfileNameMultiWords]: mockProfileData, - }); - }); - - it("returns data for profile containing multiple entries", () => { - const mockProfileDataMultipleEntries = { key1: "value1", key2: "value2", key3: "value3" }; - const mockInput = getMockProfileData(mockProfileName, mockProfileDataMultipleEntries); - expect(parseIni(mockInput)).toStrictEqual({ - [mockProfileName]: mockProfileDataMultipleEntries, - }); - }); - }); -}); diff --git a/packages/shared-ini-file-loader/src/parseIni.ts b/packages/shared-ini-file-loader/src/parseIni.ts deleted file mode 100644 index f54b1f4628be..000000000000 --- a/packages/shared-ini-file-loader/src/parseIni.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ParsedIniData } from "@aws-sdk/types"; - -const profileNameBlockList = ["__proto__", "profile __proto__"]; - -export const parseIni = (iniData: string): ParsedIniData => { - const map: ParsedIniData = {}; - let currentSection: string | undefined; - - for (let line of iniData.split(/\r?\n/)) { - line = line.split(/(^|\s)[;#]/)[0].trim(); // remove comments and trim - const isSection: boolean = line[0] === "[" && line[line.length - 1] === "]"; - if (isSection) { - currentSection = line.substring(1, line.length - 1); - if (profileNameBlockList.includes(currentSection)) { - throw new Error(`Found invalid profile name "${currentSection}"`); - } - } else if (currentSection) { - const indexOfEqualsSign = line.indexOf("="); - const start = 0; - const end: number = line.length - 1; - const isAssignment: boolean = - indexOfEqualsSign !== -1 && indexOfEqualsSign !== start && indexOfEqualsSign !== end; - if (isAssignment) { - const [name, value]: [string, string] = [ - line.substring(0, indexOfEqualsSign).trim(), - line.substring(indexOfEqualsSign + 1).trim(), - ]; - map[currentSection] = map[currentSection] || {}; - map[currentSection][name] = value; - } - } - } - - return map; -}; diff --git a/packages/shared-ini-file-loader/src/parseKnownFiles.spec.ts b/packages/shared-ini-file-loader/src/parseKnownFiles.spec.ts deleted file mode 100644 index 9803392d5463..000000000000 --- a/packages/shared-ini-file-loader/src/parseKnownFiles.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { loadSharedConfigFiles } from "./loadSharedConfigFiles"; -import { parseKnownFiles } from "./parseKnownFiles"; - -jest.mock("./loadSharedConfigFiles"); - -describe(parseKnownFiles.name, () => { - const mockConfigFile = { - profileName1: { configKey1: "configValue1" }, - profileName2: { configKey2: "configValue2" }, - }; - const mockCredentialsFile = { - profileName1: { credsKey1: "credsValue1" }, - profileName2: { credsKey2: "credsValue2" }, - }; - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("gets parsedFiles from loadSharedConfigFiles", async () => { - (loadSharedConfigFiles as jest.Mock).mockReturnValue( - Promise.resolve({ - configFile: mockConfigFile, - credentialsFile: mockCredentialsFile, - }) - ); - const mockInit = { profile: "mockProfile" }; - const parsedFiles = await parseKnownFiles(mockInit); - - expect(loadSharedConfigFiles).toHaveBeenCalledWith(mockInit); - expect(parsedFiles).toMatchInlineSnapshot(` - { - "profileName1": { - "configKey1": "configValue1", - "credsKey1": "credsValue1", - }, - "profileName2": { - "configKey2": "configValue2", - "credsKey2": "credsValue2", - }, - } - `); - }); -}); diff --git a/packages/shared-ini-file-loader/src/parseKnownFiles.ts b/packages/shared-ini-file-loader/src/parseKnownFiles.ts deleted file mode 100644 index fb9bbc99a9b3..000000000000 --- a/packages/shared-ini-file-loader/src/parseKnownFiles.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ParsedIniData } from "@aws-sdk/types"; - -import { loadSharedConfigFiles, SharedConfigInit } from "./loadSharedConfigFiles"; -import { mergeConfigFiles } from "./mergeConfigFiles"; - -export interface SourceProfileInit extends SharedConfigInit { - /** - * The configuration profile to use. - */ - profile?: string; -} - -/** - * Load profiles from credentials and config INI files and normalize them into a - * single profile list. - * - * @internal - */ -export const parseKnownFiles = async (init: SourceProfileInit): Promise => { - const parsedFiles = await loadSharedConfigFiles(init); - return mergeConfigFiles(parsedFiles.configFile, parsedFiles.credentialsFile); -}; diff --git a/packages/shared-ini-file-loader/src/slurpFile.spec.ts b/packages/shared-ini-file-loader/src/slurpFile.spec.ts deleted file mode 100644 index a1e5b87f492c..000000000000 --- a/packages/shared-ini-file-loader/src/slurpFile.spec.ts +++ /dev/null @@ -1,121 +0,0 @@ -// ToDo: Change to "fs/promises" when supporting nodejs>=14 -import { promises } from "fs"; - -jest.mock("fs", () => ({ promises: { readFile: jest.fn() } })); - -describe("slurpFile", () => { - const UTF8 = "utf8"; - const getMockFileContents = (path: string, options = UTF8) => JSON.stringify({ path, options }); - - beforeEach(() => { - (promises.readFile as jest.Mock).mockImplementation(async (path, options) => { - await new Promise((resolve) => setTimeout(resolve, 100)); - return getMockFileContents(path, options); - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("makes one readFile call for a filepath irrespective of slurpFile calls", () => { - // @ts-ignore: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34617 - it.each([10, 100, 1000, 10000])("parallel calls: %d ", (num: number, done: Function) => { - jest.isolateModules(async () => { - const { slurpFile } = require("./slurpFile"); - const mockPath = "/mock/path"; - const mockPathContent = getMockFileContents(mockPath); - - expect(promises.readFile).not.toHaveBeenCalled(); - const fileContentArr = await Promise.all(Array(num).fill(slurpFile(mockPath))); - expect(fileContentArr).toStrictEqual(Array(num).fill(mockPathContent)); - - // There is one readFile call even through slurpFile is called in parallel num times. - expect(promises.readFile).toHaveBeenCalledTimes(1); - expect(promises.readFile).toHaveBeenCalledWith(mockPath, UTF8); - done(); - }); - }); - - it("two parallel calls and one sequential call", (done) => { - jest.isolateModules(async () => { - const { slurpFile } = require("./slurpFile"); - const mockPath = "/mock/path"; - const mockPathContent = getMockFileContents(mockPath); - - expect(promises.readFile).not.toHaveBeenCalled(); - const fileContentArr = await Promise.all([slurpFile(mockPath), slurpFile(mockPath)]); - expect(fileContentArr).toStrictEqual([mockPathContent, mockPathContent]); - - // There is one readFile call even through slurpFile is called in parallel twice. - expect(promises.readFile).toHaveBeenCalledTimes(1); - expect(promises.readFile).toHaveBeenCalledWith(mockPath, UTF8); - - const fileContent = await slurpFile(mockPath); - expect(fileContent).toStrictEqual(mockPathContent); - - // There is one readFile call even through slurpFile is called for the third time. - expect(promises.readFile).toHaveBeenCalledTimes(1); - done(); - }); - }); - }); - - it("makes multiple readFile calls with based on filepaths", (done) => { - jest.isolateModules(async () => { - const { slurpFile } = require("./slurpFile"); - - const mockPath1 = "/mock/path/1"; - const mockPathContent1 = getMockFileContents(mockPath1); - - const mockPath2 = "/mock/path/2"; - const mockPathContent2 = getMockFileContents(mockPath2); - - expect(promises.readFile).not.toHaveBeenCalled(); - const fileContentArr = await Promise.all([slurpFile(mockPath1), slurpFile(mockPath2)]); - expect(fileContentArr).toStrictEqual([mockPathContent1, mockPathContent2]); - - // There are two readFile calls as slurpFile is called in parallel with different filepaths. - expect(promises.readFile).toHaveBeenCalledTimes(2); - expect(promises.readFile).toHaveBeenNthCalledWith(1, mockPath1, UTF8); - expect(promises.readFile).toHaveBeenNthCalledWith(2, mockPath2, UTF8); - - const fileContent1 = await slurpFile(mockPath1); - expect(fileContent1).toStrictEqual(mockPathContent1); - const fileContent2 = await slurpFile(mockPath2); - expect(fileContent2).toStrictEqual(mockPathContent2); - - // There is one readFile call even through slurpFile is called for the third time. - expect(promises.readFile).toHaveBeenCalledTimes(2); - done(); - }); - }); - - it("makes multiple readFile calls when called with ignoreCache option", (done) => { - jest.isolateModules(async () => { - const { slurpFile } = require("./slurpFile"); - - const mockPath1 = "/mock/path/1"; - const mockPathContent1 = getMockFileContents(mockPath1); - - expect(promises.readFile).not.toHaveBeenCalled(); - const fileContentArr = await Promise.all([ - slurpFile(mockPath1, { ignoreCache: true }), - slurpFile(mockPath1, { ignoreCache: true }), - ]); - expect(fileContentArr).toStrictEqual([mockPathContent1, mockPathContent1]); - - // There are two readFile calls as slurpFile is called in parallel with the same filepath. - expect(promises.readFile).toHaveBeenCalledTimes(2); - expect(promises.readFile).toHaveBeenNthCalledWith(1, mockPath1, UTF8); - expect(promises.readFile).toHaveBeenNthCalledWith(2, mockPath1, UTF8); - - const fileContent1 = await slurpFile(mockPath1); - expect(fileContent1).toStrictEqual(mockPathContent1); - - // There is no readFile call since slurpFile is now called without refresh. - expect(promises.readFile).toHaveBeenCalledTimes(2); - done(); - }); - }); -}); diff --git a/packages/shared-ini-file-loader/src/slurpFile.ts b/packages/shared-ini-file-loader/src/slurpFile.ts deleted file mode 100644 index 74f017d470e1..000000000000 --- a/packages/shared-ini-file-loader/src/slurpFile.ts +++ /dev/null @@ -1,17 +0,0 @@ -// ToDo: Change to "fs/promises" when supporting nodejs>=14 -import { promises as fsPromises } from "fs"; - -const { readFile } = fsPromises; - -const filePromisesHash: Record> = {}; - -interface SlurpFileOptions { - ignoreCache?: boolean; -} - -export const slurpFile = (path: string, options?: SlurpFileOptions) => { - if (!filePromisesHash[path] || options?.ignoreCache) { - filePromisesHash[path] = readFile(path, "utf8"); - } - return filePromisesHash[path]; -}; diff --git a/packages/shared-ini-file-loader/src/types.ts b/packages/shared-ini-file-loader/src/types.ts deleted file mode 100644 index 096b3877cc9f..000000000000 --- a/packages/shared-ini-file-loader/src/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - ParsedIniData as __ParsedIniData, - Profile as __Profile, - SharedConfigFiles as __SharedConfigFiles, -} from "@aws-sdk/types"; - -/** - * @deprecated Use Profile from "@aws-sdk/types" instead - */ -export type Profile = __Profile; - -/** - * @deprecated Use ParsedIniData from "@aws-sdk/types" instead - */ -export type ParsedIniData = __ParsedIniData; - -/** - * @deprecated Use SharedConfigFiles from "@aws-sdk/types" instead - */ -export type SharedConfigFiles = __SharedConfigFiles; diff --git a/packages/signature-v4/jest.config.js b/packages/signature-v4/jest.config.js deleted file mode 100644 index 64f3d932819c..000000000000 --- a/packages/signature-v4/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, - //only test cjs dist, avoid testing the package twice - testPathIgnorePatterns: ["/node_modules/", "/es/"], -}; diff --git a/packages/signature-v4/package.json b/packages/signature-v4/package.json index 06078f7d16b9..90e1b1c11bda 100644 --- a/packages/signature-v4/package.json +++ b/packages/signature-v4/package.json @@ -14,7 +14,7 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "extract:docs": "api-extractor run --local", - "test": "jest --coverage" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", @@ -22,13 +22,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/eventstream-codec": "*", - "@aws-sdk/is-array-buffer": "*", - "@aws-sdk/types": "*", - "@aws-sdk/util-hex-encoding": "*", - "@aws-sdk/util-middleware": "*", - "@aws-sdk/util-uri-escape": "*", - "@aws-sdk/util-utf8": "*", + "@smithy/signature-v4": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/signature-v4/src/SignatureV4.spec.ts b/packages/signature-v4/src/SignatureV4.spec.ts deleted file mode 100644 index 29449efeb5d6..000000000000 --- a/packages/signature-v4/src/SignatureV4.spec.ts +++ /dev/null @@ -1,851 +0,0 @@ -import { Sha256 } from "@aws-crypto/sha256-js"; -import { HttpRequest } from "@aws-sdk/protocol-http"; -import { AwsCredentialIdentity, SignableMessage, TimestampHeaderValue } from "@aws-sdk/types"; - -import { - ALGORITHM_IDENTIFIER, - ALGORITHM_QUERY_PARAM, - AMZ_DATE_HEADER, - AMZ_DATE_QUERY_PARAM, - AUTH_HEADER, - CREDENTIAL_QUERY_PARAM, - EXPIRES_QUERY_PARAM, - HOST_HEADER, - SIGNATURE_QUERY_PARAM, - SIGNED_HEADERS_QUERY_PARAM, - TOKEN_HEADER, - TOKEN_QUERY_PARAM, - UNSIGNED_PAYLOAD, -} from "./constants"; -import { SignatureV4 } from "./SignatureV4"; -import { iso8601 } from "./utilDate"; - -const signerInit = { - service: "foo", - region: "us-bar-1", - sha256: Sha256, - credentials: { - accessKeyId: "foo", - secretAccessKey: "bar", - }, -}; - -const signer = new SignatureV4(signerInit); - -const minimalRequest = new HttpRequest({ - method: "POST", - protocol: "https:", - path: "/", - headers: { - host: "foo.us-bar-1.amazonaws.com", - }, - hostname: "foo.us-bar-1.amazonaws.com", -}); - -const credentials: AwsCredentialIdentity = { - accessKeyId: "foo", - secretAccessKey: "bar", -}; - -describe("SignatureV4", () => { - describe("#presignRequest", () => { - const presigningOptions = { - expiresIn: 1800, - signingDate: new Date("2000-01-01T00:00:00.000Z"), - }; - - it("should throw on invalid credential", async () => { - const signer = new SignatureV4({ ...signerInit, credentials: {} as any }); - try { - await signer.presign(minimalRequest, presigningOptions); - fail("This test is expected to fail"); - } catch (e) { - expect(e.message).toBe("Resolved credential object is not valid"); - } - }); - - it("should sign requests without bodies", async () => { - const { query } = await signer.presign(minimalRequest, presigningOptions); - expect(query).toEqual({ - [ALGORITHM_QUERY_PARAM]: ALGORITHM_IDENTIFIER, - [CREDENTIAL_QUERY_PARAM]: "foo/20000101/us-bar-1/foo/aws4_request", - [AMZ_DATE_QUERY_PARAM]: "20000101T000000Z", - [EXPIRES_QUERY_PARAM]: presigningOptions.expiresIn.toString(), - [SIGNED_HEADERS_QUERY_PARAM]: HOST_HEADER, - [SIGNATURE_QUERY_PARAM]: "46f0091f3e84cbd4552a184f43830a4f8b42fd18ceaefcdc2c225be1efd9e00e", - }); - }); - - it("should sign request without hoisting some headers", async () => { - const { query, headers } = await signer.presign( - { - ...minimalRequest, - headers: { - ...minimalRequest.headers, - "x-amz-not-hoisted": "test", - }, - }, - { ...presigningOptions, unhoistableHeaders: new Set(["x-amz-not-hoisted"]) } - ); - expect(query).toEqual({ - [ALGORITHM_QUERY_PARAM]: ALGORITHM_IDENTIFIER, - [CREDENTIAL_QUERY_PARAM]: "foo/20000101/us-bar-1/foo/aws4_request", - [AMZ_DATE_QUERY_PARAM]: "20000101T000000Z", - [EXPIRES_QUERY_PARAM]: presigningOptions.expiresIn.toString(), - [SIGNED_HEADERS_QUERY_PARAM]: `${HOST_HEADER};x-amz-not-hoisted`, - [SIGNATURE_QUERY_PARAM]: "3c3ef586754b111e9528009710b797a07457d6a671058ba89041a06bab45f585", - }); - expect(headers).toMatchObject({ - "x-amz-not-hoisted": "test", - }); - }); - - it("should support overriding region and service in the signer instance", async () => { - const signer = new SignatureV4({ - ...signerInit, - service: "qux", - region: "us-foo-1", - }); - const { query } = await signer.presign(minimalRequest, { - ...presigningOptions, - signingService: signerInit.service, - signingRegion: signerInit.region, - }); - expect(query).toEqual({ - [ALGORITHM_QUERY_PARAM]: ALGORITHM_IDENTIFIER, - [CREDENTIAL_QUERY_PARAM]: "foo/20000101/us-bar-1/foo/aws4_request", - [AMZ_DATE_QUERY_PARAM]: "20000101T000000Z", - [EXPIRES_QUERY_PARAM]: presigningOptions.expiresIn.toString(), - [SIGNED_HEADERS_QUERY_PARAM]: HOST_HEADER, - [SIGNATURE_QUERY_PARAM]: "46f0091f3e84cbd4552a184f43830a4f8b42fd18ceaefcdc2c225be1efd9e00e", - }); - }); - - it("should default expires to 3600 seconds if not explicitly passed", async () => { - const { query } = await signer.presign(minimalRequest); - expect(query).toMatchObject({ - [EXPIRES_QUERY_PARAM]: "3600", - }); - }); - - it("should sign requests with string bodies", async () => { - const { query } = await signer.presign( - new HttpRequest({ - ...minimalRequest, - body: "It was the best of times, it was the worst of times", - }), - presigningOptions - ); - expect(query).toEqual({ - [ALGORITHM_QUERY_PARAM]: ALGORITHM_IDENTIFIER, - [CREDENTIAL_QUERY_PARAM]: "foo/20000101/us-bar-1/foo/aws4_request", - [AMZ_DATE_QUERY_PARAM]: "20000101T000000Z", - [EXPIRES_QUERY_PARAM]: presigningOptions.expiresIn.toString(), - [SIGNED_HEADERS_QUERY_PARAM]: HOST_HEADER, - [SIGNATURE_QUERY_PARAM]: "3a7fc2cef9cab09384d0ef7a69bab0d942996846422bd041da5e52cae82612c3", - }); - }); - - it("should sign requests with binary bodies", async () => { - const { query } = await signer.presign( - new HttpRequest({ - ...minimalRequest, - body: new Uint8Array([0xde, 0xad, 0xbe, 0xef]), - }), - presigningOptions - ); - expect(query).toEqual({ - [ALGORITHM_QUERY_PARAM]: ALGORITHM_IDENTIFIER, - [CREDENTIAL_QUERY_PARAM]: "foo/20000101/us-bar-1/foo/aws4_request", - [AMZ_DATE_QUERY_PARAM]: "20000101T000000Z", - [EXPIRES_QUERY_PARAM]: presigningOptions.expiresIn.toString(), - [SIGNED_HEADERS_QUERY_PARAM]: HOST_HEADER, - [SIGNATURE_QUERY_PARAM]: "bd1427cfdc9a3b0a55609b0114d1dab4dfebca81a9496d6c47dedf65a3ec3bcb", - }); - }); - - it("should sign requests with streaming (unsigned) bodies", async () => { - /** - * An environment specific stream that the signer knows nothing about. - */ - class ExoticStream {} - - const { query } = await signer.presign( - new HttpRequest({ - ...minimalRequest, - body: new ExoticStream() as any, - }), - presigningOptions - ); - expect(query).toEqual({ - [ALGORITHM_QUERY_PARAM]: ALGORITHM_IDENTIFIER, - [CREDENTIAL_QUERY_PARAM]: "foo/20000101/us-bar-1/foo/aws4_request", - [AMZ_DATE_QUERY_PARAM]: "20000101T000000Z", - [EXPIRES_QUERY_PARAM]: presigningOptions.expiresIn.toString(), - [SIGNED_HEADERS_QUERY_PARAM]: HOST_HEADER, - [SIGNATURE_QUERY_PARAM]: "457d44313f7b225c3523ddfc0ca161dfd010269b98c837a7a6f1b26ceb87ae4c", - }); - }); - - it(`should set and sign the ${TOKEN_QUERY_PARAM} query parameter if the credentials have a session token`, async () => { - const signer = new SignatureV4({ - service: "foo", - region: "us-bar-1", - sha256: Sha256, - credentials: { - ...credentials, - sessionToken: "baz", - }, - }); - const { query } = await signer.presign(minimalRequest, presigningOptions); - - expect(query).toEqual({ - [TOKEN_QUERY_PARAM]: "baz", - [ALGORITHM_QUERY_PARAM]: ALGORITHM_IDENTIFIER, - [CREDENTIAL_QUERY_PARAM]: "foo/20000101/us-bar-1/foo/aws4_request", - [AMZ_DATE_QUERY_PARAM]: "20000101T000000Z", - [EXPIRES_QUERY_PARAM]: presigningOptions.expiresIn.toString(), - [SIGNED_HEADERS_QUERY_PARAM]: HOST_HEADER, - [SIGNATURE_QUERY_PARAM]: "1b57912615b8e7ae78790ba713193d34baa793d6be2a1b18370dd27dce2d05a7", - }); - }); - - it("should use the precalculated payload checksum if provided", async () => { - const signer = new SignatureV4({ - service: "foo", - region: "us-bar-1", - sha256: Sha256, - credentials, - }); - - const { query } = await signer.presign( - new HttpRequest({ - ...minimalRequest, - body: new Uint8Array([0xde, 0xad, 0xbe, 0xef]), - headers: { - ...minimalRequest.headers, - "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD", - }, - }), - presigningOptions - ); - - expect(query).toEqual({ - "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD", - [ALGORITHM_QUERY_PARAM]: ALGORITHM_IDENTIFIER, - [CREDENTIAL_QUERY_PARAM]: "foo/20000101/us-bar-1/foo/aws4_request", - [AMZ_DATE_QUERY_PARAM]: "20000101T000000Z", - [EXPIRES_QUERY_PARAM]: presigningOptions.expiresIn.toString(), - [SIGNED_HEADERS_QUERY_PARAM]: HOST_HEADER, - [SIGNATURE_QUERY_PARAM]: "04ccc7891757c0ca3811d0e018e4655919ef11fa7b956fe9b782f273cec2374f", - }); - }); - - it("should allow specifying custom unsignable headers", async () => { - const headers = { - host: "foo.us-bar-1.amazonaws.com", - foo: "bar", - "user-agent": "baz", - }; - const { headers: headersAsSigned, query } = await signer.presign( - new HttpRequest({ - ...minimalRequest, - headers, - }), - { - ...presigningOptions, - unsignableHeaders: new Set(["foo"]), - } - ); - expect((query as any)[SIGNED_HEADERS_QUERY_PARAM]).toBe("host"); - expect(headersAsSigned).toEqual(headers); - }); - - it("should return a rejected promise if the expiresIn is more than one week in the future", async () => { - await expect( - signer.presign(minimalRequest, { - ...presigningOptions, - expiresIn: 7 * 24 * 60 * 60 + 1, - }) - ).rejects.toMatch(/less than one week in the future/); - }); - - it("should support presigning with asynchronously resolved credentials", async () => { - const credsProvider = () => - Promise.resolve({ - accessKeyId: "foo", - secretAccessKey: "bar", - }); - - const signer = new SignatureV4({ - service: "foo", - region: "us-bar-1", - sha256: Sha256, - credentials: credsProvider, - }); - - const { query } = await signer.presign(minimalRequest, presigningOptions); - - expect(query).toMatchObject({ - [CREDENTIAL_QUERY_PARAM]: "foo/20000101/us-bar-1/foo/aws4_request", - }); - }); - - it("should support presigning with an asynchronously resolved region", async () => { - const regionProvider = () => Promise.resolve("us-bar-1"); - - const signer = new SignatureV4({ - service: "foo", - region: regionProvider, - sha256: Sha256, - credentials: { - accessKeyId: "foo", - secretAccessKey: "bar", - }, - }); - - const { query } = await signer.presign(minimalRequest, presigningOptions); - - expect(query).toMatchObject({ - [CREDENTIAL_QUERY_PARAM]: "foo/20000101/us-bar-1/foo/aws4_request", - }); - }); - - describe("URI encoding paths", () => { - const minimalRequest = new HttpRequest({ - method: "POST", - protocol: "https:", - path: "/foo%3Dbar", - headers: { - host: "foo.us-bar-1.amazonaws.com", - }, - hostname: "foo.us-bar-1.amazonaws.com", - }); - - it("should URI-encode the path by default", async () => { - const { query = {} } = await signer.presign(minimalRequest, presigningOptions); - expect(query[SIGNATURE_QUERY_PARAM]).toBe("6267d8b6f44d165d2b9f4d2c2b45fd6971de0962820243669bf685818c9c7849"); - }); - - it("should normalize relative path by default", async () => { - const { query = {} } = await signer.presign( - { ...minimalRequest, path: "/abc/../foo%3Dbar" }, - presigningOptions - ); - expect(query[SIGNATURE_QUERY_PARAM]).toBe("6267d8b6f44d165d2b9f4d2c2b45fd6971de0962820243669bf685818c9c7849"); - }); - - it("should normalize path with consecutive slashes by default", async () => { - const { query = {} } = await signer.presign({ ...minimalRequest, path: "//foo%3Dbar" }, presigningOptions); - expect(query[SIGNATURE_QUERY_PARAM]).toBe("6267d8b6f44d165d2b9f4d2c2b45fd6971de0962820243669bf685818c9c7849"); - }); - - it("should not URI-encode the path if URI path escaping was disabled on the signer", async () => { - // Setting `uriEscapePath` to `false` creates an - // S3-compatible signer. The expected signature included - // below was calculated using the - // `Aws\Signature\S3SignatureV4` class from the AWS SDK for - // PHP - const signer = new SignatureV4({ - service: "foo", - region: "us-bar-1", - sha256: Sha256, - credentials: { - accessKeyId: "foo", - secretAccessKey: "bar", - }, - uriEscapePath: false, - }); - - const { query = {} } = await signer.presign( - new HttpRequest({ - ...minimalRequest, - path: "/foo/bar/baz", - headers: { - ...minimalRequest.headers, - "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD", - }, - }), - presigningOptions - ); - expect(query[SIGNATURE_QUERY_PARAM]).toBe("d1a68eff5d8d5be581f20c7793a67a6cd2e561a5b818055b21ad064139eb83b1"); - }); - }); - }); - - describe("#sign (request)", () => { - it("should throw on invalid credential", async () => { - const signer = new SignatureV4({ ...signerInit, credentials: {} as any }); - try { - await signer.sign(minimalRequest); - fail("This test is expected to fail"); - } catch (e) { - expect(e.message).toBe("Resolved credential object is not valid"); - } - }); - - it("should sign requests without bodies", async () => { - const { headers } = await signer.sign(minimalRequest, { - signingDate: new Date("2000-01-01T00:00:00.000Z"), - }); - expect(headers[AUTH_HEADER]).toBe( - "AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=1e3b24fcfd7655c0c245d99ba7b6b5ca6174eab903ebfbda09ce457af062ad30" - ); - }); - - it("should support overriding region and service in the signer instance", async () => { - const signer = new SignatureV4({ - ...signerInit, - service: "qux", - region: "us-foo-1", - }); - const { headers } = await signer.sign(minimalRequest, { - signingDate: new Date("2000-01-01T00:00:00.000Z"), - signingService: signerInit.service, - signingRegion: signerInit.region, - }); - expect(headers[AUTH_HEADER]).toBe( - "AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=1e3b24fcfd7655c0c245d99ba7b6b5ca6174eab903ebfbda09ce457af062ad30" - ); - }); - - it("should sign requests without host header", async () => { - const request = minimalRequest.clone(); - delete request.headers[HOST_HEADER]; - const { headers } = await signer.sign(request, { - signingDate: new Date("2000-01-01T00:00:00.000Z"), - }); - expect(headers[AUTH_HEADER]).toBe( - "AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=x-amz-content-sha256;x-amz-date, Signature=36cfca5cdb2c8d094f100663925d408a9608908ffc10b83133e5b25829ef7f5f" - ); - }); - - it("should sign requests with string bodies", async () => { - const { headers } = await signer.sign( - new HttpRequest({ - ...minimalRequest, - body: "It was the best of times, it was the worst of times", - }), - { signingDate: new Date("2000-01-01T00:00:00.000Z") } - ); - expect(headers[AUTH_HEADER]).toBe( - "AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=cf22a0befff359388f136b158f0b1b43db7b18d2ca65ce4112bc88a16815c4b6" - ); - }); - - it("should sign requests with binary bodies", async () => { - const { headers } = await signer.sign( - new HttpRequest({ - ...minimalRequest, - body: new Uint8Array([0xde, 0xad, 0xbe, 0xef]), - }), - { signingDate: new Date("2000-01-01T00:00:00.000Z") } - ); - expect(headers[AUTH_HEADER]).toBe( - "AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=89f092f52faedb8a6be1890b2a511b88e7998389d62bd7d72915e2f4ee271a64" - ); - }); - - it("should sign requests with streaming (unsigned) bodies", async () => { - /** - * An environment specific stream that the signer knows nothing about. - */ - class ExoticStream {} - const { headers } = await signer.sign( - new HttpRequest({ - ...minimalRequest, - body: new ExoticStream() as any, - headers: { - ...minimalRequest.headers, - "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD", - }, - }), - { signingDate: new Date("2000-01-01T00:00:00.000Z") } - ); - - expect(headers[AUTH_HEADER]).toBe( - "AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=2d17bf1aa1624819549626389790503937599b27a998286e0e190b897b1467dd" - ); - expect(headers["X-Amz-Content-Sha256"]).toBe(UNSIGNED_PAYLOAD); - }); - - it("should sign requests with unsigned bodies when so directed", async () => { - const { headers } = await signer.sign( - new HttpRequest({ - ...minimalRequest, - body: "It was the best of times, it was the worst of times", - headers: { - ...minimalRequest.headers, - "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD", - }, - }), - { signingDate: new Date("2000-01-01T00:00:00.000Z") } - ); - - expect(headers[AUTH_HEADER]).toBe( - "AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=2d17bf1aa1624819549626389790503937599b27a998286e0e190b897b1467dd" - ); - expect(headers["X-Amz-Content-Sha256"]).toBe(UNSIGNED_PAYLOAD); - }); - - it(`should set the ${AMZ_DATE_HEADER}`, async () => { - const { headers } = await signer.sign(minimalRequest, { - signingDate: new Date("2000-01-01T00:00:00.000Z"), - }); - expect(headers[AMZ_DATE_HEADER]).toBe("20000101T000000Z"); - }); - - it(`should set and sign the ${TOKEN_HEADER} header if the credentials have a session token`, async () => { - const signer = new SignatureV4({ - service: "foo", - region: "us-bar-1", - sha256: Sha256, - credentials: { - ...credentials, - sessionToken: "baz", - }, - }); - const { headers } = await signer.sign(minimalRequest, { - signingDate: new Date("2000-01-01T00:00:00.000Z"), - }); - expect(headers[AUTH_HEADER]).toBe( - "AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token, Signature=4fd09a8cf3b28a62a9c6c424f03ababcd703528578bc6ec9184fc585f18c3fbb" - ); - }); - - it("should allow specifying custom unsignable headers", async () => { - const { headers } = await signer.sign( - new HttpRequest({ - ...minimalRequest, - headers: { - host: "foo.us-bar-1.amazonaws.com", - foo: "bar", - "user-agent": "baz", - }, - }), - { - signingDate: new Date("2000-01-01T00:00:00.000Z"), - unsignableHeaders: new Set(["foo"]), - } - ); - expect(headers[AUTH_HEADER]).toMatch( - /^AWS4-HMAC-SHA256 Credential=foo\/20000101\/us-bar-1\/foo\/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=/ - ); - }); - - it("should allow specifying custom signable headers to override custom and always unsignable ones", async () => { - const { headers } = await signer.sign( - { - ...minimalRequest, - headers: { - host: "foo.us-bar-1.amazonaws.com", - foo: "bar", - "user-agent": "baz", - }, - }, - { - signingDate: new Date("2000-01-01T00:00:00.000Z"), - unsignableHeaders: new Set(["foo"]), - signableHeaders: new Set(["foo", "user-agent"]), - } - ); - expect(headers[AUTH_HEADER]).toMatch( - /^AWS4-HMAC-SHA256 Credential=foo\/20000101\/us-bar-1\/foo\/aws4_request, SignedHeaders=foo;host;user-agent;x-amz-content-sha256;x-amz-date, Signature=/ - ); - }); - - it("should support signing with asynchronously resolved credentials", async () => { - const credsProvider = () => - Promise.resolve({ - accessKeyId: "foo", - secretAccessKey: "bar", - }); - - const signer = new SignatureV4({ - service: "foo", - region: "us-bar-1", - sha256: Sha256, - credentials: credsProvider, - }); - - const { headers } = await signer.sign(minimalRequest, { - signingDate: new Date("2000-01-01T00:00:00.000Z"), - }); - - expect(headers[AUTH_HEADER]).toMatch(/^AWS4-HMAC-SHA256 Credential=foo\/20000101\/us-bar-1\/foo\/aws4_request/); - }); - - it("should support presigning with an asynchronously resolved region", async () => { - const regionProvider = () => Promise.resolve("us-bar-1"); - - const signer = new SignatureV4({ - service: "foo", - region: regionProvider, - sha256: Sha256, - credentials: { - accessKeyId: "foo", - secretAccessKey: "bar", - }, - }); - - const { headers } = await signer.sign(minimalRequest, { - signingDate: new Date("2000-01-01T00:00:00.000Z"), - }); - - expect(headers[AUTH_HEADER]).toMatch(/^AWS4-HMAC-SHA256 Credential=foo\/20000101\/us-bar-1\/foo\/aws4_request/); - }); - - describe("URI encoding paths", () => { - const minimalRequest = new HttpRequest({ - method: "POST", - protocol: "https:", - path: "/foo%3Dbar", - headers: { - host: "foo.us-bar-1.amazonaws.com", - }, - hostname: "foo.us-bar-1.amazonaws.com", - }); - - const signingOptions = { - signingDate: new Date("2000-01-01T00:00:00.000Z"), - }; - - it("should URI-encode the path by default", async () => { - const { headers } = await signer.sign(minimalRequest, signingOptions); - expect(headers[AUTH_HEADER]).toBe( - "AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=fb4948cab44a9c47ce3b1a2489d01ec939fea9e79eccdb4593c11a94f207e075" - ); - }); - - it("should normalize relative path by default", async () => { - const { headers } = await signer.sign({ ...minimalRequest, path: "/abc/../foo%3Dbar" }, signingOptions); - expect(headers[AUTH_HEADER]).toEqual( - expect.stringContaining("Signature=fb4948cab44a9c47ce3b1a2489d01ec939fea9e79eccdb4593c11a94f207e075") - ); - }); - - it("should normalize path with consecutive slashes by default", async () => { - const { headers } = await signer.sign({ ...minimalRequest, path: "//foo%3Dbar" }, signingOptions); - expect(headers[AUTH_HEADER]).toEqual( - expect.stringContaining("Signature=fb4948cab44a9c47ce3b1a2489d01ec939fea9e79eccdb4593c11a94f207e075") - ); - }); - - it("should not URI-encode the path if URI path escaping was disabled on the signer", async () => { - // Setting `uriEscapePath` to `false` creates an - // S3-compatible signer. The expected authorization header - // included below was calculated using the - // `Aws\Signature\S3SignatureV4` class from the AWS SDK for - // PHP - const signer = new SignatureV4({ - service: "foo", - region: "us-bar-1", - sha256: Sha256, - credentials: { - accessKeyId: "foo", - secretAccessKey: "bar", - }, - uriEscapePath: false, - }); - - const { headers } = await signer.sign( - new HttpRequest({ - ...minimalRequest, - headers: { - ...minimalRequest.headers, - "X-Amz-Content-Sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - }, - }), - { - signingDate: new Date("2000-01-01T00:00:00.000Z"), - } - ); - expect(headers[AUTH_HEADER]).toBe( - "AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=0d859e5a74374efc2c9f14ba9352df14c68e411a1f44bd639fdd024e5f7b7ef1" - ); - }); - }); - }); - - describe("#sign (string)", () => { - const signerInit = { - service: "s3", - region: "us-east-1", - credentials: { - accessKeyId: "AKIAIOSFODNN7EXAMPLE", - secretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - }, - sha256: Sha256, - }; - - it("should throw on invalid credential", async () => { - const signer = new SignatureV4({ ...signerInit, credentials: {} as any }); - try { - await signer.sign("STRING_TO_SIGN"); - fail("This test is expected to fail"); - } catch (e) { - expect(e.message).toBe("Resolved credential object is not valid"); - } - }); - - it("should produce signatures matching known outputs", async () => { - // Example copied from https://github.com/aws/aws-sdk-php/blob/3.42.0/tests/S3/PostObjectV4Test.php#L37 - const signer = new SignatureV4(signerInit); - const signingDate = new Date("2015-12-29T00:00:00Z"); - const stringToSign = - "eyJleHBpcmF0aW9uIjoiMjAxNS0xMi0yOVQwMTowMDowMFoiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJzaWd2NGV4YW1wbGVidWNrZXQifSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXJcL3VzZXIxXC8iXSx7ImFjbCI6InB1YmxpYy1yZWFkIn0seyJzdWNjZXNzX2FjdGlvbl9yZWRpcmVjdCI6Imh0dHA6XC9cL3NpZ3Y0ZXhhbXBsZWJ1Y2tldC5zMy5hbWF6b25hd3MuY29tXC9zdWNjZXNzZnVsX3VwbG9hZC5odG1sIn0sWyJzdGFydHMtd2l0aCIsIiRDb250ZW50LVR5cGUiLCJpbWFnZVwvIl0seyJ4LWFtei1tZXRhLXV1aWQiOiIxNDM2NTEyMzY1MTI3NCJ9LHsieC1hbXotc2VydmVyLXNpZGUtZW5jcnlwdGlvbiI6IkFFUzI1NiJ9LFsic3RhcnRzLXdpdGgiLCIkeC1hbXotbWV0YS10YWciLCIiXSx7IlgtQW16LURhdGUiOiIyMDE1MTIyOVQwMDAwWiJ9LHsiWC1BbXotQ3JlZGVudGlhbCI6IkFLSUFJT1NGT0ROTjdFWEFNUExFXC8yMDE1MTIyOVwvdXMtZWFzdC0xXC9zM1wvYXdzNF9yZXF1ZXN0In0seyJYLUFtei1BbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In1dfQ=="; - expect(await signer.sign(stringToSign, { signingDate })).toBe( - "683963a1575bb197c642490ac60f3f08cda08233cd3a163ad31b554e9327a3ff" - ); - }); - - it("should support overriding region and service in the signer instance", async () => { - const signer = new SignatureV4({ - ...signerInit, - service: "qux", - region: "us-foo-1", - }); - const signingDate = new Date("2015-12-29T00:00:00Z"); - const stringToSign = - "eyJleHBpcmF0aW9uIjoiMjAxNS0xMi0yOVQwMTowMDowMFoiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJzaWd2NGV4YW1wbGVidWNrZXQifSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXJcL3VzZXIxXC8iXSx7ImFjbCI6InB1YmxpYy1yZWFkIn0seyJzdWNjZXNzX2FjdGlvbl9yZWRpcmVjdCI6Imh0dHA6XC9cL3NpZ3Y0ZXhhbXBsZWJ1Y2tldC5zMy5hbWF6b25hd3MuY29tXC9zdWNjZXNzZnVsX3VwbG9hZC5odG1sIn0sWyJzdGFydHMtd2l0aCIsIiRDb250ZW50LVR5cGUiLCJpbWFnZVwvIl0seyJ4LWFtei1tZXRhLXV1aWQiOiIxNDM2NTEyMzY1MTI3NCJ9LHsieC1hbXotc2VydmVyLXNpZGUtZW5jcnlwdGlvbiI6IkFFUzI1NiJ9LFsic3RhcnRzLXdpdGgiLCIkeC1hbXotbWV0YS10YWciLCIiXSx7IlgtQW16LURhdGUiOiIyMDE1MTIyOVQwMDAwWiJ9LHsiWC1BbXotQ3JlZGVudGlhbCI6IkFLSUFJT1NGT0ROTjdFWEFNUExFXC8yMDE1MTIyOVwvdXMtZWFzdC0xXC9zM1wvYXdzNF9yZXF1ZXN0In0seyJYLUFtei1BbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In1dfQ=="; - expect( - await signer.sign(stringToSign, { - signingDate, - signingRegion: signerInit.region, - signingService: signerInit.service, - }) - ).toBe("683963a1575bb197c642490ac60f3f08cda08233cd3a163ad31b554e9327a3ff"); - }); - }); - - describe("#sign (event)", () => { - //adopt to Ruby SDK: https://github.com/aws/aws-sdk-ruby/blob/3c47c05aa77bdbb7b803a3ff932b3a89c32276ac/gems/aws-sigv4/spec/signer_spec.rb#L274 - const signerInit = { - service: "SERVICE", - region: "REGION", - credentials: { - accessKeyId: "akid", - secretAccessKey: "secret", - }, - sha256: Sha256, - }; - - it("should throw on invalid credential", async () => { - const signer = new SignatureV4({ ...signerInit, credentials: {} as any }); - try { - await signer.sign( - { - headers: Uint8Array.from([5, 58, 100, 97, 116, 101, 8, 0, 0, 1, 103, 247, 125, 87, 112]), - payload: "foo" as any, - }, - { - signingDate: new Date(1369353600000), - priorSignature: "", - } - ); - fail("This test is expected to fail"); - } catch (e) { - expect(e.message).toBe("Resolved credential object is not valid"); - } - }); - - it("support event signing", async () => { - const signer = new SignatureV4(signerInit); - const eventSignature = await signer.sign( - { - headers: Uint8Array.from([5, 58, 100, 97, 116, 101, 8, 0, 0, 1, 103, 247, 125, 87, 112]), - payload: "foo" as any, - }, - { - signingDate: new Date(1369353600000), - priorSignature: "", - } - ); - expect(eventSignature).toEqual("204bb5e2713e95354680e9522986d3ac0304aeafd33397f39e6540ca51ffe226"); - }); - - it("should support overriding region and service in the signer instance", async () => { - const signer = new SignatureV4({ - ...signerInit, - service: "qux", - // region: "us-foo-1", - }); - const eventSignature = await signer.sign( - { - headers: Uint8Array.from([5, 58, 100, 97, 116, 101, 8, 0, 0, 1, 103, 247, 125, 87, 112]), - payload: "foo" as any, - }, - { - signingDate: new Date(1369353600000), - priorSignature: "", - // signingRegion: signerInit.region, - signingService: signerInit.service, - } - ); - expect(eventSignature).toEqual("204bb5e2713e95354680e9522986d3ac0304aeafd33397f39e6540ca51ffe226"); - }); - }); - - describe("#sign (message)", () => { - const signerInit = { - service: "SERVICE", - region: "REGION", - credentials: { - accessKeyId: "akid", - secretAccessKey: "secret", - }, - sha256: Sha256, - }; - - it("support message signing", async () => { - const signer = new SignatureV4(signerInit); - - const headers = { - ":date": { - type: "timestamp", - value: new Date("2018-12-29T01:04:06.000Z"), - } as TimestampHeaderValue, - }; - - const signedMessage = await signer.sign( - { - message: { - headers, - body: "foo" as any, - }, - priorSignature: "", - } as SignableMessage, - { - signingDate: new Date(1369353600000), - } - ); - expect(signedMessage.signature).toEqual("204bb5e2713e95354680e9522986d3ac0304aeafd33397f39e6540ca51ffe226"); - expect(signedMessage.message.body).toEqual("foo"); - expect(signedMessage.message.headers).toEqual(headers); - }); - }); - - describe("ambient Date usage", () => { - let dateSpy; - const mockDate = new Date(); - - beforeEach(() => { - dateSpy = jest.spyOn(global, "Date").mockImplementation(() => mockDate); - }); - - afterEach(() => { - expect(dateSpy).toHaveBeenCalledTimes(1); - jest.clearAllMocks(); - }); - - it("should use the current date for presigning if no signing date was supplied", async () => { - const { query } = await signer.presign(minimalRequest); - expect((query as any)[AMZ_DATE_QUERY_PARAM]).toBe(iso8601(mockDate).replace(/[\-:]/g, "")); - }); - - it("should use the current date for signing if no signing date supplied", async () => { - const { headers } = await signer.sign(minimalRequest); - expect(headers[AMZ_DATE_HEADER]).toBe(iso8601(mockDate).replace(/[\-:]/g, "")); - }); - }); -}); diff --git a/packages/signature-v4/src/SignatureV4.ts b/packages/signature-v4/src/SignatureV4.ts deleted file mode 100644 index 4c984737a3e8..000000000000 --- a/packages/signature-v4/src/SignatureV4.ts +++ /dev/null @@ -1,387 +0,0 @@ -import { HeaderMarshaller } from "@aws-sdk/eventstream-codec"; -import { - AwsCredentialIdentity, - ChecksumConstructor, - DateInput, - EventSigner, - EventSigningArguments, - FormattedEvent, - HashConstructor, - HeaderBag, - HttpRequest, - MessageSigner, - Provider, - RequestPresigner, - RequestPresigningArguments, - RequestSigner, - RequestSigningArguments, - SignableMessage, - SignedMessage, - SigningArguments, - StringSigner, -} from "@aws-sdk/types"; -import { toHex } from "@aws-sdk/util-hex-encoding"; -import { normalizeProvider } from "@aws-sdk/util-middleware"; -import { fromUtf8, toUint8Array, toUtf8 } from "@aws-sdk/util-utf8"; - -import { - ALGORITHM_IDENTIFIER, - ALGORITHM_QUERY_PARAM, - AMZ_DATE_HEADER, - AMZ_DATE_QUERY_PARAM, - AUTH_HEADER, - CREDENTIAL_QUERY_PARAM, - EVENT_ALGORITHM_IDENTIFIER, - EXPIRES_QUERY_PARAM, - MAX_PRESIGNED_TTL, - SHA256_HEADER, - SIGNATURE_QUERY_PARAM, - SIGNED_HEADERS_QUERY_PARAM, - TOKEN_HEADER, - TOKEN_QUERY_PARAM, -} from "./constants"; -import { createScope, getSigningKey } from "./credentialDerivation"; -import { getCanonicalHeaders } from "./getCanonicalHeaders"; -import { getCanonicalQuery } from "./getCanonicalQuery"; -import { getPayloadHash } from "./getPayloadHash"; -import { hasHeader } from "./headerUtil"; -import { moveHeadersToQuery } from "./moveHeadersToQuery"; -import { prepareRequest } from "./prepareRequest"; -import { iso8601 } from "./utilDate"; - -export interface SignatureV4Init { - /** - * The service signing name. - */ - service: string; - - /** - * The region name or a function that returns a promise that will be - * resolved with the region name. - */ - region: string | Provider; - - /** - * The credentials with which the request should be signed or a function - * that returns a promise that will be resolved with credentials. - */ - credentials: AwsCredentialIdentity | Provider; - - /** - * A constructor function for a hash object that will calculate SHA-256 HMAC - * checksums. - */ - sha256?: ChecksumConstructor | HashConstructor; - - /** - * Whether to uri-escape the request URI path as part of computing the - * canonical request string. This is required for every AWS service, except - * Amazon S3, as of late 2017. - * - * @default [true] - */ - uriEscapePath?: boolean; - - /** - * Whether to calculate a checksum of the request body and include it as - * either a request header (when signing) or as a query string parameter - * (when presigning). This is required for AWS Glacier and Amazon S3 and optional for - * every other AWS service as of late 2017. - * - * @default [true] - */ - applyChecksum?: boolean; -} - -export interface SignatureV4CryptoInit { - sha256: ChecksumConstructor | HashConstructor; -} - -export class SignatureV4 implements RequestPresigner, RequestSigner, StringSigner, EventSigner, MessageSigner { - private readonly service: string; - private readonly regionProvider: Provider; - private readonly credentialProvider: Provider; - private readonly sha256: ChecksumConstructor | HashConstructor; - private readonly uriEscapePath: boolean; - private readonly applyChecksum: boolean; - private readonly headerMarshaller = new HeaderMarshaller(toUtf8, fromUtf8); - - constructor({ - applyChecksum, - credentials, - region, - service, - sha256, - uriEscapePath = true, - }: SignatureV4Init & SignatureV4CryptoInit) { - this.service = service; - this.sha256 = sha256; - this.uriEscapePath = uriEscapePath; - // default to true if applyChecksum isn't set - this.applyChecksum = typeof applyChecksum === "boolean" ? applyChecksum : true; - this.regionProvider = normalizeProvider(region); - this.credentialProvider = normalizeProvider(credentials); - } - - public async presign(originalRequest: HttpRequest, options: RequestPresigningArguments = {}): Promise { - const { - signingDate = new Date(), - expiresIn = 3600, - unsignableHeaders, - unhoistableHeaders, - signableHeaders, - signingRegion, - signingService, - } = options; - const credentials = await this.credentialProvider(); - this.validateResolvedCredentials(credentials); - const region = signingRegion ?? (await this.regionProvider()); - - const { longDate, shortDate } = formatDate(signingDate); - if (expiresIn > MAX_PRESIGNED_TTL) { - return Promise.reject( - "Signature version 4 presigned URLs" + " must have an expiration date less than one week in" + " the future" - ); - } - - const scope = createScope(shortDate, region, signingService ?? this.service); - const request = moveHeadersToQuery(prepareRequest(originalRequest), { unhoistableHeaders }); - - if (credentials.sessionToken) { - request.query[TOKEN_QUERY_PARAM] = credentials.sessionToken; - } - request.query[ALGORITHM_QUERY_PARAM] = ALGORITHM_IDENTIFIER; - request.query[CREDENTIAL_QUERY_PARAM] = `${credentials.accessKeyId}/${scope}`; - request.query[AMZ_DATE_QUERY_PARAM] = longDate; - request.query[EXPIRES_QUERY_PARAM] = expiresIn.toString(10); - - const canonicalHeaders = getCanonicalHeaders(request, unsignableHeaders, signableHeaders); - request.query[SIGNED_HEADERS_QUERY_PARAM] = getCanonicalHeaderList(canonicalHeaders); - - request.query[SIGNATURE_QUERY_PARAM] = await this.getSignature( - longDate, - scope, - this.getSigningKey(credentials, region, shortDate, signingService), - this.createCanonicalRequest(request, canonicalHeaders, await getPayloadHash(originalRequest, this.sha256)) - ); - - return request; - } - - public async sign(stringToSign: string, options?: SigningArguments): Promise; - public async sign(event: FormattedEvent, options: EventSigningArguments): Promise; - public async sign(event: SignableMessage, options: SigningArguments): Promise; - public async sign(requestToSign: HttpRequest, options?: RequestSigningArguments): Promise; - public async sign(toSign: any, options: any): Promise { - if (typeof toSign === "string") { - return this.signString(toSign, options); - } else if (toSign.headers && toSign.payload) { - return this.signEvent(toSign, options); - } else if (toSign.message) { - return this.signMessage(toSign, options); - } else { - return this.signRequest(toSign, options); - } - } - - private async signEvent( - { headers, payload }: FormattedEvent, - { signingDate = new Date(), priorSignature, signingRegion, signingService }: EventSigningArguments - ): Promise { - const region = signingRegion ?? (await this.regionProvider()); - const { shortDate, longDate } = formatDate(signingDate); - const scope = createScope(shortDate, region, signingService ?? this.service); - const hashedPayload = await getPayloadHash({ headers: {}, body: payload } as any, this.sha256); - const hash = new this.sha256(); - hash.update(headers); - const hashedHeaders = toHex(await hash.digest()); - const stringToSign = [ - EVENT_ALGORITHM_IDENTIFIER, - longDate, - scope, - priorSignature, - hashedHeaders, - hashedPayload, - ].join("\n"); - return this.signString(stringToSign, { signingDate, signingRegion: region, signingService }); - } - - async signMessage( - signableMessage: SignableMessage, - { signingDate = new Date(), signingRegion, signingService }: SigningArguments - ): Promise { - const promise = this.signEvent( - { - headers: this.headerMarshaller.format(signableMessage.message.headers), - payload: signableMessage.message.body, - }, - { - signingDate, - signingRegion, - signingService, - priorSignature: signableMessage.priorSignature, - } - ); - - return promise.then((signature) => { - return { message: signableMessage.message, signature }; - }); - } - - private async signString( - stringToSign: string, - { signingDate = new Date(), signingRegion, signingService }: SigningArguments = {} - ): Promise { - const credentials = await this.credentialProvider(); - this.validateResolvedCredentials(credentials); - const region = signingRegion ?? (await this.regionProvider()); - const { shortDate } = formatDate(signingDate); - - const hash = new this.sha256(await this.getSigningKey(credentials, region, shortDate, signingService)); - hash.update(toUint8Array(stringToSign)); - return toHex(await hash.digest()); - } - - private async signRequest( - requestToSign: HttpRequest, - { - signingDate = new Date(), - signableHeaders, - unsignableHeaders, - signingRegion, - signingService, - }: RequestSigningArguments = {} - ): Promise { - const credentials = await this.credentialProvider(); - this.validateResolvedCredentials(credentials); - const region = signingRegion ?? (await this.regionProvider()); - const request = prepareRequest(requestToSign); - const { longDate, shortDate } = formatDate(signingDate); - const scope = createScope(shortDate, region, signingService ?? this.service); - - request.headers[AMZ_DATE_HEADER] = longDate; - if (credentials.sessionToken) { - request.headers[TOKEN_HEADER] = credentials.sessionToken; - } - - const payloadHash = await getPayloadHash(request, this.sha256); - if (!hasHeader(SHA256_HEADER, request.headers) && this.applyChecksum) { - request.headers[SHA256_HEADER] = payloadHash; - } - - const canonicalHeaders = getCanonicalHeaders(request, unsignableHeaders, signableHeaders); - const signature = await this.getSignature( - longDate, - scope, - this.getSigningKey(credentials, region, shortDate, signingService), - this.createCanonicalRequest(request, canonicalHeaders, payloadHash) - ); - - request.headers[AUTH_HEADER] = - `${ALGORITHM_IDENTIFIER} ` + - `Credential=${credentials.accessKeyId}/${scope}, ` + - `SignedHeaders=${getCanonicalHeaderList(canonicalHeaders)}, ` + - `Signature=${signature}`; - - return request; - } - - private createCanonicalRequest(request: HttpRequest, canonicalHeaders: HeaderBag, payloadHash: string): string { - const sortedHeaders = Object.keys(canonicalHeaders).sort(); - return `${request.method} -${this.getCanonicalPath(request)} -${getCanonicalQuery(request)} -${sortedHeaders.map((name) => `${name}:${canonicalHeaders[name]}`).join("\n")} - -${sortedHeaders.join(";")} -${payloadHash}`; - } - - private async createStringToSign( - longDate: string, - credentialScope: string, - canonicalRequest: string - ): Promise { - const hash = new this.sha256(); - hash.update(toUint8Array(canonicalRequest)); - const hashedRequest = await hash.digest(); - - return `${ALGORITHM_IDENTIFIER} -${longDate} -${credentialScope} -${toHex(hashedRequest)}`; - } - - private getCanonicalPath({ path }: HttpRequest): string { - if (this.uriEscapePath) { - // Non-S3 services, we normalize the path and then double URI encode it. - // Ref: "Remove Dot Segments" https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4 - const normalizedPathSegments = []; - for (const pathSegment of path.split("/")) { - if (pathSegment?.length === 0) continue; - if (pathSegment === ".") continue; - if (pathSegment === "..") { - normalizedPathSegments.pop(); - } else { - normalizedPathSegments.push(pathSegment); - } - } - // Joining by single slashes to remove consecutive slashes. - const normalizedPath = `${path?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${ - normalizedPathSegments.length > 0 && path?.endsWith("/") ? "/" : "" - }`; - - const doubleEncoded = encodeURIComponent(normalizedPath); - return doubleEncoded.replace(/%2F/g, "/"); - } - - // For S3, we shouldn't normalize the path. For example, object name - // my-object//example//photo.user should not be normalized to - // my-object/example/photo.user - return path; - } - - private async getSignature( - longDate: string, - credentialScope: string, - keyPromise: Promise, - canonicalRequest: string - ): Promise { - const stringToSign = await this.createStringToSign(longDate, credentialScope, canonicalRequest); - - const hash = new this.sha256(await keyPromise); - hash.update(toUint8Array(stringToSign)); - return toHex(await hash.digest()); - } - - private getSigningKey( - credentials: AwsCredentialIdentity, - region: string, - shortDate: string, - service?: string - ): Promise { - return getSigningKey(this.sha256, credentials, shortDate, region, service || this.service); - } - - private validateResolvedCredentials(credentials: unknown) { - if ( - typeof credentials !== "object" || - // @ts-expect-error: Property 'accessKeyId' does not exist on type 'object'.ts(2339) - typeof credentials.accessKeyId !== "string" || - // @ts-expect-error: Property 'secretAccessKey' does not exist on type 'object'.ts(2339) - typeof credentials.secretAccessKey !== "string" - ) { - throw new Error("Resolved credential object is not valid"); - } - } -} - -const formatDate = (now: DateInput): { longDate: string; shortDate: string } => { - const longDate = iso8601(now).replace(/[\-:]/g, ""); - return { - longDate, - shortDate: longDate.slice(0, 8), - }; -}; - -const getCanonicalHeaderList = (headers: object): string => Object.keys(headers).sort().join(";"); diff --git a/packages/signature-v4/src/cloneRequest.spec.ts b/packages/signature-v4/src/cloneRequest.spec.ts deleted file mode 100644 index 17cb93b07d78..000000000000 --- a/packages/signature-v4/src/cloneRequest.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { HttpRequest, QueryParameterBag } from "@aws-sdk/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 d21d39cd6ddb..000000000000 --- a/packages/signature-v4/src/cloneRequest.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { HttpRequest, QueryParameterBag } from "@aws-sdk/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/constants.ts b/packages/signature-v4/src/constants.ts deleted file mode 100644 index e66b35d6367f..000000000000 --- a/packages/signature-v4/src/constants.ts +++ /dev/null @@ -1,53 +0,0 @@ -export const ALGORITHM_QUERY_PARAM = "X-Amz-Algorithm"; -export const CREDENTIAL_QUERY_PARAM = "X-Amz-Credential"; -export const AMZ_DATE_QUERY_PARAM = "X-Amz-Date"; -export const SIGNED_HEADERS_QUERY_PARAM = "X-Amz-SignedHeaders"; -export const EXPIRES_QUERY_PARAM = "X-Amz-Expires"; -export const SIGNATURE_QUERY_PARAM = "X-Amz-Signature"; -export const TOKEN_QUERY_PARAM = "X-Amz-Security-Token"; -export const REGION_SET_PARAM = "X-Amz-Region-Set"; - -export const AUTH_HEADER = "authorization"; -export const AMZ_DATE_HEADER = AMZ_DATE_QUERY_PARAM.toLowerCase(); -export const DATE_HEADER = "date"; -export const GENERATED_HEADERS = [AUTH_HEADER, AMZ_DATE_HEADER, DATE_HEADER]; -export const SIGNATURE_HEADER = SIGNATURE_QUERY_PARAM.toLowerCase(); -export const SHA256_HEADER = "x-amz-content-sha256"; -export const TOKEN_HEADER = TOKEN_QUERY_PARAM.toLowerCase(); -export const HOST_HEADER = "host"; - -export const ALWAYS_UNSIGNABLE_HEADERS = { - authorization: true, - "cache-control": true, - connection: true, - expect: true, - from: true, - "keep-alive": true, - "max-forwards": true, - pragma: true, - referer: true, - te: true, - trailer: true, - "transfer-encoding": true, - upgrade: true, - "user-agent": true, - "x-amzn-trace-id": true, -}; - -export const PROXY_HEADER_PATTERN = /^proxy-/; - -export const SEC_HEADER_PATTERN = /^sec-/; - -export const UNSIGNABLE_PATTERNS = [/^proxy-/i, /^sec-/i]; - -export const ALGORITHM_IDENTIFIER = "AWS4-HMAC-SHA256"; -export const ALGORITHM_IDENTIFIER_V4A = "AWS4-ECDSA-P256-SHA256"; - -export const EVENT_ALGORITHM_IDENTIFIER = "AWS4-HMAC-SHA256-PAYLOAD"; - -export const UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"; - -export const MAX_CACHE_SIZE = 50; -export const KEY_TYPE_IDENTIFIER = "aws4_request"; - -export const MAX_PRESIGNED_TTL = 60 * 60 * 24 * 7; diff --git a/packages/signature-v4/src/credentialDerivation.spec.ts b/packages/signature-v4/src/credentialDerivation.spec.ts deleted file mode 100644 index 23897838d5c1..000000000000 --- a/packages/signature-v4/src/credentialDerivation.spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Sha256 } from "@aws-crypto/sha256-js"; -import { AwsCredentialIdentity } from "@aws-sdk/types"; -import { toHex } from "@aws-sdk/util-hex-encoding"; - -import { clearCredentialCache, createScope, getSigningKey } from "./credentialDerivation"; - -describe("createScope", () => { - it("should create a scoped identifier for the credentials used", () => { - expect(createScope("date", "region", "service")).toBe("date/region/service/aws4_request"); - }); -}); - -describe("getSigningKey", () => { - beforeEach(clearCredentialCache); - - const credentials: AwsCredentialIdentity = { - accessKeyId: "foo", - secretAccessKey: "bar", - }; - const shortDate = "19700101"; - const region = "us-foo-1"; - const service = "bar"; - - it( - "should return a buffer containing a signing key derived from the" + - " provided credentials, date, region, and service", - () => { - return expect(getSigningKey(Sha256, credentials, shortDate, region, service).then(toHex)).resolves.toBe( - "b7c34d23320b5cd909500c889eac033a33c93f5a4bf67f71988a58f299e62e0a" - ); - } - ); - - it("should trap errors encountered while hashing", () => { - return expect( - getSigningKey( - jest.fn(() => { - throw new Error("PANIC"); - }), - credentials, - shortDate, - region, - service - ) - ).rejects.toMatchObject(new Error("PANIC")); - }); - - describe("caching", () => { - it("should return the same signing key when called with the same date, region, service, and credentials", async () => { - const mockSha256Constructor = jest.fn().mockImplementation((args) => { - return new Sha256(args); - }); - const key1 = await getSigningKey(mockSha256Constructor, credentials, shortDate, region, service); - const key2 = await getSigningKey(mockSha256Constructor, credentials, shortDate, region, service); - expect(key1).toBe(key2); - expect(mockSha256Constructor).toHaveBeenCalledTimes(6); - }); - - it("should cache a maximum of 50 entries", async () => { - const keys: Array = new Array(50); - // fill the cache - for (let i = 0; i < 50; i++) { - keys[i] = await getSigningKey(Sha256, credentials, shortDate, `us-foo-${i.toString(10)}`, service); - } - - // evict the oldest member from the cache - await getSigningKey(Sha256, credentials, shortDate, `us-foo-50`, service); - - // the second oldest member should still be in cache - await expect(getSigningKey(Sha256, credentials, shortDate, `us-foo-1`, service)).resolves.toStrictEqual(keys[1]); - - // the oldest member should not be in the cache - await expect(getSigningKey(Sha256, credentials, shortDate, `us-foo-0`, service)).resolves.not.toBe(keys[0]); - }); - }); -}); diff --git a/packages/signature-v4/src/credentialDerivation.ts b/packages/signature-v4/src/credentialDerivation.ts deleted file mode 100644 index d6eb1d308ad6..000000000000 --- a/packages/signature-v4/src/credentialDerivation.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { AwsCredentialIdentity, ChecksumConstructor, HashConstructor, SourceData } from "@aws-sdk/types"; -import { toHex } from "@aws-sdk/util-hex-encoding"; -import { toUint8Array } from "@aws-sdk/util-utf8"; - -import { KEY_TYPE_IDENTIFIER, MAX_CACHE_SIZE } from "./constants"; - -const signingKeyCache: Record = {}; -const cacheQueue: Array = []; - -/** - * Create a string describing the scope of credentials used to sign a request. - * - * @param shortDate The current calendar date in the form YYYYMMDD. - * @param region The AWS region in which the service resides. - * @param service The service to which the signed request is being sent. - */ -export const createScope = (shortDate: string, region: string, service: string): string => - `${shortDate}/${region}/${service}/${KEY_TYPE_IDENTIFIER}`; - -/** - * Derive a signing key from its composite parts - * - * @param sha256Constructor A constructor function that can instantiate SHA-256 - * hash objects. - * @param credentials The credentials with which the request will be - * signed. - * @param shortDate The current calendar date in the form YYYYMMDD. - * @param region The AWS region in which the service resides. - * @param service The service to which the signed request is being - * sent. - */ -export const getSigningKey = async ( - sha256Constructor: ChecksumConstructor | HashConstructor, - credentials: AwsCredentialIdentity, - shortDate: string, - region: string, - service: string -): Promise => { - const credsHash = await hmac(sha256Constructor, credentials.secretAccessKey, credentials.accessKeyId); - const cacheKey = `${shortDate}:${region}:${service}:${toHex(credsHash)}:${credentials.sessionToken}`; - if (cacheKey in signingKeyCache) { - return signingKeyCache[cacheKey]; - } - - cacheQueue.push(cacheKey); - while (cacheQueue.length > MAX_CACHE_SIZE) { - delete signingKeyCache[cacheQueue.shift() as string]; - } - - let key: SourceData = `AWS4${credentials.secretAccessKey}`; - for (const signable of [shortDate, region, service, KEY_TYPE_IDENTIFIER]) { - key = await hmac(sha256Constructor, key, signable); - } - return (signingKeyCache[cacheKey] = key as Uint8Array); -}; - -/** - * @internal - */ -export const clearCredentialCache = (): void => { - cacheQueue.length = 0; - Object.keys(signingKeyCache).forEach((cacheKey) => { - delete signingKeyCache[cacheKey]; - }); -}; - -const hmac = ( - ctor: ChecksumConstructor | HashConstructor, - secret: SourceData, - data: SourceData -): Promise => { - const hash = new ctor(secret); - hash.update(toUint8Array(data)); - return hash.digest(); -}; diff --git a/packages/signature-v4/src/getCanonicalHeaders.spec.ts b/packages/signature-v4/src/getCanonicalHeaders.spec.ts deleted file mode 100644 index c0567f1a17bb..000000000000 --- a/packages/signature-v4/src/getCanonicalHeaders.spec.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { HttpRequest } from "@aws-sdk/protocol-http"; -import { HeaderBag } from "@aws-sdk/types"; - -import { ALWAYS_UNSIGNABLE_HEADERS } from "./constants"; -import { getCanonicalHeaders } from "./getCanonicalHeaders"; - -describe("getCanonicalHeaders", () => { - it("should downcase all headers", () => { - expect( - getCanonicalHeaders( - new HttpRequest({ - method: "POST", - protocol: "https:", - path: "/", - headers: { - fOo: "bar", - BaZ: "QUUX", - HoSt: "foo.us-east-1.amazonaws.com", - }, - hostname: "foo.us-east-1.amazonaws.com", - }) - ) - ).toEqual({ - foo: "bar", - baz: "QUUX", - host: "foo.us-east-1.amazonaws.com", - }); - }); - - it("should remove all unsignable headers", () => { - const request = new HttpRequest({ - method: "POST", - protocol: "https:", - path: "/", - headers: { - "x-amz-user-agent": "aws-sdk-js-v3", - host: "foo.us-east-1.amazonaws.com", - foo: "bar", - }, - hostname: "foo.us-east-1.amazonaws.com", - }); - for (const headerName of Object.keys(ALWAYS_UNSIGNABLE_HEADERS)) { - request.headers[headerName] = "baz"; - } - - expect(getCanonicalHeaders(request)).toEqual({ - "x-amz-user-agent": "aws-sdk-js-v3", - host: "foo.us-east-1.amazonaws.com", - foo: "bar", - }); - }); - - it("should ignore headers with undefined values", () => { - const headers: HeaderBag = { - "x-amz-user-agent": "aws-sdk-js-v3", - host: "foo.us-east-1.amazonaws.com", - ":authority": "", - }; - - const request = new HttpRequest({ - method: "POST", - protocol: "https:", - path: "/", - headers: { - ...headers, - foo: undefined, - bar: null, - }, - hostname: "foo.us-east-1.amazonaws.com", - }); - - expect(getCanonicalHeaders(request)).toEqual(headers); - }); - - it("should allow specifying custom unsignable headers", () => { - const request = new HttpRequest({ - method: "POST", - protocol: "https:", - path: "/", - headers: { - host: "foo.us-east-1.amazonaws.com", - foo: "bar", - "user-agent": "foo-user", - }, - hostname: "foo.us-east-1.amazonaws.com", - }); - - expect(getCanonicalHeaders(request, new Set(["foo"]))).toEqual({ - host: "foo.us-east-1.amazonaws.com", - }); - }); - - it("should allow specifying custom signable headers that override unsignable ones", () => { - const request = new HttpRequest({ - method: "POST", - protocol: "https:", - path: "/", - headers: { - host: "foo.us-east-1.amazonaws.com", - foo: "bar", - "user-agent": "foo-user", - }, - hostname: "foo.us-east-1.amazonaws.com", - }); - - expect(getCanonicalHeaders(request, new Set(["foo"]), new Set(["foo", "user-agent"]))).toEqual({ - host: "foo.us-east-1.amazonaws.com", - foo: "bar", - "user-agent": "foo-user", - }); - }); -}); diff --git a/packages/signature-v4/src/getCanonicalHeaders.ts b/packages/signature-v4/src/getCanonicalHeaders.ts deleted file mode 100644 index 564c81fe76a8..000000000000 --- a/packages/signature-v4/src/getCanonicalHeaders.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { HeaderBag, HttpRequest } from "@aws-sdk/types"; - -import { ALWAYS_UNSIGNABLE_HEADERS, PROXY_HEADER_PATTERN, SEC_HEADER_PATTERN } from "./constants"; - -/** - * @private - */ -export const getCanonicalHeaders = ( - { headers }: HttpRequest, - unsignableHeaders?: Set, - signableHeaders?: Set -): HeaderBag => { - const canonical: HeaderBag = {}; - for (const headerName of Object.keys(headers).sort()) { - if (headers[headerName] == undefined) { - continue; - } - - const canonicalHeaderName = headerName.toLowerCase(); - if ( - canonicalHeaderName in ALWAYS_UNSIGNABLE_HEADERS || - unsignableHeaders?.has(canonicalHeaderName) || - PROXY_HEADER_PATTERN.test(canonicalHeaderName) || - SEC_HEADER_PATTERN.test(canonicalHeaderName) - ) { - if (!signableHeaders || (signableHeaders && !signableHeaders.has(canonicalHeaderName))) { - continue; - } - } - - canonical[canonicalHeaderName] = headers[headerName].trim().replace(/\s+/g, " "); - } - - return canonical; -}; diff --git a/packages/signature-v4/src/getCanonicalQuery.spec.ts b/packages/signature-v4/src/getCanonicalQuery.spec.ts deleted file mode 100644 index bc856740a458..000000000000 --- a/packages/signature-v4/src/getCanonicalQuery.spec.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { HttpRequest } from "@aws-sdk/protocol-http"; - -import { getCanonicalQuery } from "./getCanonicalQuery"; - -const httpRequestOptions = { - method: "POST", - protocol: "https:", - path: "/", - headers: {}, - hostname: "foo.us-east-1.amazonaws.com", -}; - -describe("getCanonicalQuery", () => { - it("should return an empty string for requests with no querystring", () => { - expect(getCanonicalQuery(new HttpRequest(httpRequestOptions))).toBe(""); - }); - - it("should serialize simple key => value pairs", () => { - expect( - getCanonicalQuery( - new HttpRequest({ - ...httpRequestOptions, - query: { fizz: "buzz", foo: "bar" }, - }) - ) - ).toBe("fizz=buzz&foo=bar"); - }); - - it("should sort query keys alphabetically", () => { - expect( - getCanonicalQuery( - new HttpRequest({ - ...httpRequestOptions, - query: { foo: "bar", baz: "quux", fizz: "buzz" }, - }) - ) - ).toBe("baz=quux&fizz=buzz&foo=bar"); - }); - - it("should URI-encode keys and values", () => { - expect( - getCanonicalQuery( - new HttpRequest({ - ...httpRequestOptions, - query: { "🐎": "🦄", "💩": "☃️" }, - }) - ) - ).toBe("%F0%9F%90%8E=%F0%9F%A6%84&%F0%9F%92%A9=%E2%98%83%EF%B8%8F"); - }); - - it("should omit the x-amz-signature parameter, regardless of case", () => { - expect( - getCanonicalQuery( - new HttpRequest({ - ...httpRequestOptions, - query: { - "x-amz-signature": "foo", - "X-Amz-Signature": "bar", - fizz: "buzz", - }, - }) - ) - ).toBe("fizz=buzz"); - }); - - it("should serialize arrays of values", () => { - expect( - getCanonicalQuery( - new HttpRequest({ - ...httpRequestOptions, - query: { foo: ["bar", "baz"] }, - }) - ) - ).toBe("foo=bar&foo=baz"); - }); - - it("should serialize arrays using an alphabetic sort", () => { - expect( - getCanonicalQuery( - new HttpRequest({ - ...httpRequestOptions, - query: { snap: ["pop", "crackle"] }, - }) - ) - ).toBe("snap=crackle&snap=pop"); - }); - - it("should URI-encode members of query param arrays", () => { - expect( - getCanonicalQuery( - new HttpRequest({ - ...httpRequestOptions, - query: { "🐎": ["💩", "🦄"] }, - }) - ) - ).toBe("%F0%9F%90%8E=%F0%9F%92%A9&%F0%9F%90%8E=%F0%9F%A6%84"); - }); - - it("should omit non-string, non-array values from the serialized query", () => { - expect( - getCanonicalQuery( - new HttpRequest({ - ...httpRequestOptions, - query: { foo: "bar", baz: new Uint8Array(0) as any }, - }) - ) - ).toBe("foo=bar"); - }); -}); diff --git a/packages/signature-v4/src/getCanonicalQuery.ts b/packages/signature-v4/src/getCanonicalQuery.ts deleted file mode 100644 index 1577df019332..000000000000 --- a/packages/signature-v4/src/getCanonicalQuery.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { HttpRequest } from "@aws-sdk/types"; -import { escapeUri } from "@aws-sdk/util-uri-escape"; - -import { SIGNATURE_HEADER } from "./constants"; - -/** - * @private - */ -export const getCanonicalQuery = ({ query = {} }: HttpRequest): string => { - const keys: Array = []; - const serialized: Record = {}; - for (const key of Object.keys(query).sort()) { - if (key.toLowerCase() === SIGNATURE_HEADER) { - continue; - } - - keys.push(key); - const value = query[key]; - if (typeof value === "string") { - serialized[key] = `${escapeUri(key)}=${escapeUri(value)}`; - } else if (Array.isArray(value)) { - serialized[key] = value - .slice(0) - .sort() - .reduce( - (encoded: Array, value: string) => encoded.concat([`${escapeUri(key)}=${escapeUri(value)}`]), - [] - ) - .join("&"); - } - } - - return keys - .map((key) => serialized[key]) - .filter((serialized) => serialized) // omit any falsy values - .join("&"); -}; diff --git a/packages/signature-v4/src/getPayloadHash.spec.ts b/packages/signature-v4/src/getPayloadHash.spec.ts deleted file mode 100644 index 4d9903dedf7a..000000000000 --- a/packages/signature-v4/src/getPayloadHash.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Sha256 } from "@aws-crypto/sha256-js"; -import { HttpRequest } from "@aws-sdk/protocol-http"; - -import { SHA256_HEADER, UNSIGNED_PAYLOAD } from "./constants"; -import { getPayloadHash } from "./getPayloadHash"; - -describe("getPayloadHash", () => { - const minimalRequest = new HttpRequest({ - method: "POST", - protocol: "https:", - path: "/", - headers: {}, - hostname: "foo.us-east-1.amazonaws.com", - }); - - it("should return the SHA-256 hash of an empty string if a request has no payload (body)", async () => { - await expect(getPayloadHash(minimalRequest, Sha256)).resolves.toBe( - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - ); - }); - - it(`should return the value in the '${SHA256_HEADER}' header (if present)`, async () => { - await expect( - getPayloadHash( - new HttpRequest({ - ...minimalRequest, - headers: { - [SHA256_HEADER]: "foo", - }, - }), - jest.fn(() => { - throw new Error("I should not have been invoked!"); - }) - ) - ).resolves.toBe("foo"); - }); - - it("should return the hex-encoded hash of a string body", async () => { - await expect( - getPayloadHash( - new HttpRequest({ - ...minimalRequest, - body: "foo", - }), - Sha256 - ) - ).resolves.toBe("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"); - }); - - it("should return the hex-encoded hash of a ArrayBufferView body", async () => { - await expect( - getPayloadHash( - new HttpRequest({ - ...minimalRequest, - body: new Uint8Array([0xde, 0xad, 0xbe, 0xef]), - }), - Sha256 - ) - ).resolves.toBe("5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953"); - }); - - it("should return the hex-encoded hash of a ArrayBuffer body", async () => { - await expect( - getPayloadHash( - new HttpRequest({ - ...minimalRequest, - body: new Uint8Array([0xde, 0xad, 0xbe, 0xef]).buffer, - }), - Sha256 - ) - ).resolves.toBe("5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953"); - }); - - it(`should return ${UNSIGNED_PAYLOAD} if the request has a streaming body and no stream collector is provided`, async () => { - /** - * An environment specific stream that the signer knows nothing about. - */ - class ExoticStream {} - - await expect( - getPayloadHash( - new HttpRequest({ - ...minimalRequest, - body: new ExoticStream() as any, - }), - Sha256 - ) - ).resolves.toBe(UNSIGNED_PAYLOAD); - }); -}); diff --git a/packages/signature-v4/src/getPayloadHash.ts b/packages/signature-v4/src/getPayloadHash.ts deleted file mode 100644 index 4394c39d34e2..000000000000 --- a/packages/signature-v4/src/getPayloadHash.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { isArrayBuffer } from "@aws-sdk/is-array-buffer"; -import { ChecksumConstructor, HashConstructor, HttpRequest } from "@aws-sdk/types"; -import { toHex } from "@aws-sdk/util-hex-encoding"; -import { toUint8Array } from "@aws-sdk/util-utf8"; - -import { SHA256_HEADER, UNSIGNED_PAYLOAD } from "./constants"; - -/** - * @private - */ -export const getPayloadHash = async ( - { headers, body }: HttpRequest, - hashConstructor: ChecksumConstructor | HashConstructor -): Promise => { - for (const headerName of Object.keys(headers)) { - if (headerName.toLowerCase() === SHA256_HEADER) { - return headers[headerName]; - } - } - - if (body == undefined) { - return "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; - } else if (typeof body === "string" || ArrayBuffer.isView(body) || isArrayBuffer(body)) { - const hashCtor = new hashConstructor(); - hashCtor.update(toUint8Array(body)); - return toHex(await hashCtor.digest()); - } - - // As any defined body that is not a string or binary data is a stream, this - // body is unsignable. Attempt to send the request with an unsigned payload, - // which may or may not be accepted by the service. - return UNSIGNED_PAYLOAD; -}; diff --git a/packages/signature-v4/src/headerUtil.ts b/packages/signature-v4/src/headerUtil.ts deleted file mode 100644 index c4423c511fdd..000000000000 --- a/packages/signature-v4/src/headerUtil.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { HeaderBag } from "@aws-sdk/types"; - -export const hasHeader = (soughtHeader: string, headers: HeaderBag): boolean => { - soughtHeader = soughtHeader.toLowerCase(); - for (const headerName of Object.keys(headers)) { - if (soughtHeader === headerName.toLowerCase()) { - return true; - } - } - - return false; -}; - -/* Get the value of one request header, ignore the case. Return string if header is in the headers, else return undefined */ -export const getHeaderValue = (soughtHeader: string, headers: HeaderBag): string | undefined => { - soughtHeader = soughtHeader.toLowerCase(); - for (const headerName of Object.keys(headers)) { - if (soughtHeader === headerName.toLowerCase()) { - return headers[headerName]; - } - } - - return undefined; -}; - -/* Delete the one request header, ignore the case. Do nothing if it's not there */ -export const deleteHeader = (soughtHeader: string, headers: HeaderBag) => { - soughtHeader = soughtHeader.toLowerCase(); - for (const headerName of Object.keys(headers)) { - if (soughtHeader === headerName.toLowerCase()) { - delete headers[headerName]; - } - } -}; diff --git a/packages/signature-v4/src/index.ts b/packages/signature-v4/src/index.ts index 7bb33c23b315..5ca24fd5fbd6 100644 --- a/packages/signature-v4/src/index.ts +++ b/packages/signature-v4/src/index.ts @@ -1,7 +1 @@ -export * from "./SignatureV4"; -export { getCanonicalHeaders } from "./getCanonicalHeaders"; -export { getCanonicalQuery } from "./getCanonicalQuery"; -export { getPayloadHash } from "./getPayloadHash"; -export { moveHeadersToQuery } from "./moveHeadersToQuery"; -export { prepareRequest } from "./prepareRequest"; -export * from "./credentialDerivation"; +export * from "@smithy/signature-v4"; diff --git a/packages/signature-v4/src/moveHeadersToQuery.spec.ts b/packages/signature-v4/src/moveHeadersToQuery.spec.ts deleted file mode 100644 index 77a72c415918..000000000000 --- a/packages/signature-v4/src/moveHeadersToQuery.spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { HttpRequest } from "@aws-sdk/protocol-http"; - -import { moveHeadersToQuery } from "./moveHeadersToQuery"; - -const minimalRequest = new HttpRequest({ - method: "POST", - protocol: "https:", - path: "/", - headers: { - host: "foo.us-east-1.amazonaws.com", - }, - hostname: "foo.us-east-1.amazonaws.com", -}); - -describe("moveHeadersToQuery", () => { - it('should hoist "x-amz-" headers to the querystring', () => { - const req = moveHeadersToQuery( - new HttpRequest({ - ...minimalRequest, - headers: { - Host: "www.example.com", - "X-Amz-Website-Redirect-Location": "/index.html", - Foo: "bar", - fizz: "buzz", - SNAP: "crackle, pop", - "X-Amz-Storage-Class": "STANDARD_IA", - }, - }) - ); - - expect(req.query).toEqual({ - "X-Amz-Website-Redirect-Location": "/index.html", - "X-Amz-Storage-Class": "STANDARD_IA", - }); - - expect(req.headers).toEqual({ - Host: "www.example.com", - Foo: "bar", - fizz: "buzz", - SNAP: "crackle, pop", - }); - }); - - it("should not overwrite existing query values with different keys", () => { - const req = moveHeadersToQuery( - new HttpRequest({ - ...minimalRequest, - headers: { - Host: "www.example.com", - "X-Amz-Website-Redirect-Location": "/index.html", - Foo: "bar", - fizz: "buzz", - SNAP: "crackle, pop", - "X-Amz-Storage-Class": "STANDARD_IA", - }, - query: { - Foo: "buzz", - fizz: "bar", - "X-Amz-Storage-Class": "REDUCED_REDUNDANCY", - }, - }) - ); - - expect(req.query).toEqual({ - Foo: "buzz", - fizz: "bar", - "X-Amz-Website-Redirect-Location": "/index.html", - "X-Amz-Storage-Class": "STANDARD_IA", - }); - }); - - it("should skip hoisting headers to the querystring supplied in unhoistedHeaders", () => { - const req = moveHeadersToQuery( - new HttpRequest({ - ...minimalRequest, - headers: { - Host: "www.example.com", - "X-Amz-Website-Redirect-Location": "/index.html", - Foo: "bar", - fizz: "buzz", - SNAP: "crackle, pop", - "X-Amz-Storage-Class": "STANDARD_IA", - }, - }), - { - unhoistableHeaders: new Set(["x-amz-website-redirect-location"]), - } - ); - - expect(req.query).toEqual({ - "X-Amz-Storage-Class": "STANDARD_IA", - }); - - expect(req.headers).toEqual({ - Host: "www.example.com", - "X-Amz-Website-Redirect-Location": "/index.html", - Foo: "bar", - fizz: "buzz", - SNAP: "crackle, pop", - }); - }); -}); diff --git a/packages/signature-v4/src/moveHeadersToQuery.ts b/packages/signature-v4/src/moveHeadersToQuery.ts deleted file mode 100644 index 8f2fb7e330c4..000000000000 --- a/packages/signature-v4/src/moveHeadersToQuery.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { HttpRequest, QueryParameterBag } from "@aws-sdk/types"; - -import { cloneRequest } from "./cloneRequest"; - -/** - * @private - */ -export const moveHeadersToQuery = ( - request: HttpRequest, - options: { unhoistableHeaders?: Set } = {} -): HttpRequest & { query: QueryParameterBag } => { - const { headers, query = {} as QueryParameterBag } = - typeof (request as any).clone === "function" ? (request as any).clone() : cloneRequest(request); - for (const name of Object.keys(headers)) { - const lname = name.toLowerCase(); - if (lname.slice(0, 6) === "x-amz-" && !options.unhoistableHeaders?.has(lname)) { - query[name] = headers[name]; - delete headers[name]; - } - } - - return { - ...request, - headers, - query, - }; -}; diff --git a/packages/signature-v4/src/prepareRequest.spec.ts b/packages/signature-v4/src/prepareRequest.spec.ts deleted file mode 100644 index 980ef80a8b6a..000000000000 --- a/packages/signature-v4/src/prepareRequest.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { HttpRequest } from "@aws-sdk/protocol-http"; - -import { AMZ_DATE_HEADER, AUTH_HEADER, DATE_HEADER } from "./constants"; -import { prepareRequest } from "./prepareRequest"; - -const minimalRequest = new HttpRequest({ - method: "POST", - protocol: "https:", - path: "/", - headers: { - host: "foo.us-bar-1.amazonaws.com", - }, - hostname: "foo.us-bar-1.amazonaws.com", -}); - -describe("prepareRequest", () => { - it("should clone requests", () => { - const prepared = prepareRequest(minimalRequest); - expect(prepared).toEqual(prepareRequest(minimalRequest)); - expect(prepared).not.toBe(prepareRequest(minimalRequest)); - }); - - it("should ignore previously set authorization, date, and x-amz-date headers", async () => { - const { headers } = prepareRequest( - new HttpRequest({ - ...minimalRequest, - headers: { - [AUTH_HEADER]: "foo", - [AMZ_DATE_HEADER]: "bar", - [DATE_HEADER]: "baz", - }, - }) - ); - expect(headers[AUTH_HEADER]).toBeUndefined(); - expect(headers[AMZ_DATE_HEADER]).toBeUndefined(); - expect(headers[DATE_HEADER]).toBeUndefined(); - }); -}); diff --git a/packages/signature-v4/src/prepareRequest.ts b/packages/signature-v4/src/prepareRequest.ts deleted file mode 100644 index 91bf32880496..000000000000 --- a/packages/signature-v4/src/prepareRequest.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { HttpRequest } from "@aws-sdk/types"; - -import { cloneRequest } from "./cloneRequest"; -import { GENERATED_HEADERS } from "./constants"; - -/** - * @private - */ -export const prepareRequest = (request: HttpRequest): HttpRequest => { - // 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); - - for (const headerName of Object.keys(request.headers)) { - if (GENERATED_HEADERS.indexOf(headerName.toLowerCase()) > -1) { - delete request.headers[headerName]; - } - } - - return request; -}; diff --git a/packages/signature-v4/src/suite.fixture.ts b/packages/signature-v4/src/suite.fixture.ts deleted file mode 100644 index 5ebd9a1e19d0..000000000000 --- a/packages/signature-v4/src/suite.fixture.ts +++ /dev/null @@ -1,435 +0,0 @@ -import { HttpRequest } from "@aws-sdk/types"; - -export interface TestCase { - name: string; - request: HttpRequest; - authorization: string; -} - -export const region = "us-east-1"; -export const service = "service"; -export const credentials = { - accessKeyId: "AKIDEXAMPLE", - secretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", -}; - -export const signingDate = new Date("2015-08-30T12:36:00Z"); - -export const requests: Array = [ - { - name: "get-header-key-duplicate", - request: { - protocol: "https:", - method: "GET", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "my-header1": "value2,value2,value1", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=c9d5ea9f3f72853aea855b47ea873832890dbdd183b4468f858259531a5138ea", - }, - { - name: "get-header-value-multiline", - request: { - protocol: "https:", - method: "GET", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "my-header1": "value1,value2,value3", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=ba17b383a53190154eb5fa66a1b836cc297cc0a3d70a5d00705980573d8ff790", - }, - { - name: "get-header-value-order", - request: { - protocol: "https:", - method: "GET", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "my-header1": "value4,value1,value3,value2", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=08c7e5a9acfcfeb3ab6b2185e75ce8b1deb5e634ec47601a50643f830c755c01", - }, - { - name: "get-header-value-trim", - request: { - protocol: "https:", - method: "GET", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "my-header1": "value1", - "my-header2": '"a b c"', - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;my-header2;x-amz-date, Signature=acc3ed3afb60bb290fc8d2dd0098b9911fcaa05412b367055dee359757a9c736", - }, - { - name: "get-unreserved", - request: { - protocol: "https:", - method: "GET", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=07ef7494c76fa4850883e2b006601f940f8a34d404d0cfa977f52a65bbf5f24f", - }, - { - name: "get-utf8", - request: { - protocol: "https:", - method: "GET", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/ሴ", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=8318018e0b0f223aa2bbf98705b62bb787dc9c0e678f255a891fd03141be5d85", - }, - { - name: "get-vanilla", - request: { - protocol: "https:", - method: "GET", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31", - }, - { - name: "get-vanilla-empty-query-key", - request: { - protocol: "https:", - method: "GET", - hostname: "example.amazonaws.com", - query: { - Param1: "value1", - }, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=a67d582fa61cc504c4bae71f336f98b97f1ea3c7a6bfe1b6e45aec72011b9aeb", - }, - { - name: "get-vanilla-query", - request: { - protocol: "https:", - method: "GET", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31", - }, - { - name: "get-vanilla-query-order-key-case", - request: { - protocol: "https:", - method: "GET", - hostname: "example.amazonaws.com", - query: { - Param2: "value2", - Param1: "value1", - }, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=b97d918cfa904a5beff61c982a1b6f458b799221646efd99d3219ec94cdf2500", - }, - { - name: "get-vanilla-query-unreserved", - request: { - protocol: "https:", - method: "GET", - hostname: "example.amazonaws.com", - query: { - "-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz": - "-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", - }, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=9c3e54bfcdf0b19771a7f523ee5669cdf59bc7cc0884027167c21bb143a40197", - }, - { - name: "get-vanilla-utf8-query", - request: { - protocol: "https:", - method: "GET", - hostname: "example.amazonaws.com", - query: { - ሴ: "bar", - }, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=2cdec8eed098649ff3a119c94853b13c643bcf08f8b0a1d91e12c9027818dd04", - }, - { - name: "post-header-key-case", - request: { - protocol: "https:", - method: "POST", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5da7c1a2acd57cee7505fc6676e4e544621c30862966e37dddb68e92efbe5d6b", - }, - { - name: "post-header-key-sort", - request: { - protocol: "https:", - method: "POST", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "my-header1": "value1", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=c5410059b04c1ee005303aed430f6e6645f61f4dc9e1461ec8f8916fdf18852c", - }, - { - name: "post-header-value-case", - request: { - protocol: "https:", - method: "POST", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "my-header1": "VALUE1", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=cdbc9802e29d2942e5e10b5bccfdd67c5f22c7c4e8ae67b53629efa58b974b7d", - }, - { - name: "post-sts-header-after", - request: { - protocol: "https:", - method: "POST", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5da7c1a2acd57cee7505fc6676e4e544621c30862966e37dddb68e92efbe5d6b", - }, - { - name: "post-sts-header-before", - request: { - protocol: "https:", - method: "POST", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - "x-amz-security-token": - "AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGdQrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz+scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA==", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=85d96828115b5dc0cfc3bd16ad9e210dd772bbebba041836c64533a82be05ead", - }, - { - name: "post-vanilla", - request: { - protocol: "https:", - method: "POST", - hostname: "example.amazonaws.com", - query: {}, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5da7c1a2acd57cee7505fc6676e4e544621c30862966e37dddb68e92efbe5d6b", - }, - { - name: "post-vanilla-empty-query-value", - request: { - protocol: "https:", - method: "POST", - hostname: "example.amazonaws.com", - query: { - Param1: "value1", - }, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=28038455d6de14eafc1f9222cf5aa6f1a96197d7deb8263271d420d138af7f11", - }, - { - name: "post-vanilla-query", - request: { - protocol: "https:", - method: "POST", - hostname: "example.amazonaws.com", - query: { - Param1: "value1", - }, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=28038455d6de14eafc1f9222cf5aa6f1a96197d7deb8263271d420d138af7f11", - }, - { - name: "post-vanilla-query-nonunreserved", - request: { - protocol: "https:", - method: "POST", - hostname: "example.amazonaws.com", - query: { - "@#$%^": "", - "+": '/,?><`";:\\|][{}', - }, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=66c82657c86e26fb25238d0e69f011edc4c6df5ae71119d7cb98ed9b87393c1e", - }, - { - name: "post-vanilla-query-space", - request: { - protocol: "https:", - method: "POST", - hostname: "example.amazonaws.com", - query: { - p: "", - }, - headers: { - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=e71688addb58a26418614085fb730ba3faa623b461c17f48f2fbdb9361b94a9b", - }, - { - name: "post-x-www-form-urlencoded", - request: { - protocol: "https:", - method: "POST", - hostname: "example.amazonaws.com", - query: {}, - headers: { - "content-type": "application/x-www-form-urlencoded", - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - body: "Param1=value1", - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=ff11897932ad3f4e8b18135d722051e5ac45fc38421b1da7b9d196a0fe09473a", - }, - { - name: "post-x-www-form-urlencoded-parameters", - request: { - protocol: "https:", - method: "POST", - hostname: "example.amazonaws.com", - query: {}, - headers: { - "content-type": "application/x-www-form-urlencoded; charset=utf8", - host: "example.amazonaws.com", - "x-amz-date": "20150830T123600Z", - }, - body: "Param1=value1", - path: "/", - }, - authorization: - "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=1a72ec8f64bd914b0e42e42607c7fbce7fb2c7465f63e3092b3b0d39fa77a6fe", - }, -]; diff --git a/packages/signature-v4/src/suite.spec.ts b/packages/signature-v4/src/suite.spec.ts deleted file mode 100644 index 0c98d033b167..000000000000 --- a/packages/signature-v4/src/suite.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Sha256 } from "@aws-crypto/sha256-js"; -import { HttpRequest } from "@aws-sdk/protocol-http"; - -import { SignatureV4 } from "./SignatureV4"; -import { credentials, region, requests, service, signingDate } from "./suite.fixture"; - -/** - * Executes the official AWS Signature Version 4 test suite. - * - * @link http://docs.aws.amazon.com/general/latest/gr/signature-v4-test-suite.html - */ -describe("AWS Signature Version 4 Test Suite", () => { - const signer = new SignatureV4({ - credentials, - region, - service, - sha256: Sha256, - applyChecksum: false, - }); - - for (const { name, request, authorization } of requests) { - it(`should calculate the correct signature for ${name}`, async () => { - const signed = await signer.sign(new HttpRequest(request), { - signingDate, - }); - expect(signed.headers["authorization"]).toEqual(authorization); - }); - } -}); diff --git a/packages/signature-v4/src/utilDate.spec.ts b/packages/signature-v4/src/utilDate.spec.ts deleted file mode 100644 index 813777874e2b..000000000000 --- a/packages/signature-v4/src/utilDate.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { iso8601, toDate } from "./utilDate"; - -const toIsoString = "2017-05-22T19:33:14.175Z"; -const iso8601String = "2017-05-22T19:33:14Z"; -const rfc822String = "Mon, 22 May 2017 19:33:14 GMT"; -const epochTs = 1495481594; - -describe("iso8601", () => { - it("should convert date objects to ISO-8601 strings", () => { - expect(iso8601(new Date(toIsoString))).toBe(iso8601String); - }); - - it("should convert parseable date strings to ISO-8601 strings", () => { - const date = new Date(toIsoString); - - expect(iso8601(date.toUTCString())).toBe(iso8601String); - expect(iso8601(date.toISOString())).toBe(iso8601String); - }); - - it("should assume numbers are epoch timestamps and convert them to ISO-8601 strings accordingly", () => { - expect(iso8601(epochTs)).toBe(iso8601String); - }); -}); - -describe("toDate", () => { - it("should convert epoch timestamps to date objects", () => { - const date = toDate(epochTs); - expect(date).toBeInstanceOf(Date); - expect(date.valueOf()).toBe(epochTs * 1000); - }); - - it("should convert ISO-8601 strings to date objects", () => { - const date = toDate(iso8601String); - expect(date).toBeInstanceOf(Date); - expect(date.valueOf()).toBe(epochTs * 1000); - }); - - it("should convert RFC 822 strings to date objects", () => { - const date = toDate(rfc822String); - expect(date).toBeInstanceOf(Date); - expect(date.valueOf()).toBe(epochTs * 1000); - }); -}); diff --git a/packages/signature-v4/src/utilDate.ts b/packages/signature-v4/src/utilDate.ts deleted file mode 100644 index cbb5ba69088a..000000000000 --- a/packages/signature-v4/src/utilDate.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const iso8601 = (time: number | string | Date): string => - toDate(time) - .toISOString() - .replace(/\.\d{3}Z$/, "Z"); - -export const toDate = (time: number | string | Date): Date => { - if (typeof time === "number") { - return new Date(time * 1000); - } - - if (typeof time === "string") { - if (Number(time)) { - return new Date(Number(time) * 1000); - } - return new Date(time); - } - - return time; -}; diff --git a/packages/smithy-client/.eslintrc.json b/packages/smithy-client/.eslintrc.json deleted file mode 100644 index a4ae1380eb4d..000000000000 --- a/packages/smithy-client/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "plugins": ["eslint-plugin-tsdoc"], - "rules": { - "tsdoc/syntax": "warn" - } -} diff --git a/packages/smithy-client/jest.config.js b/packages/smithy-client/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/smithy-client/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/smithy-client/package.json b/packages/smithy-client/package.json index 1a2f76809c6e..be0a14f40cb7 100644 --- a/packages/smithy-client/package.json +++ b/packages/smithy-client/package.json @@ -10,7 +10,7 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "extract:docs": "api-extractor run --local", - "test": "jest --passWithNoTests" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -21,10 +21,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-stack": "*", - "@aws-sdk/types": "*", - "@aws-sdk/util-stream": "*", - "@smithy/types": "^1.1.0", + "@smithy/smithy-client": "^1.0.3", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/smithy-client/src/NoOpLogger.ts b/packages/smithy-client/src/NoOpLogger.ts deleted file mode 100644 index 336ca3825a8a..000000000000 --- a/packages/smithy-client/src/NoOpLogger.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Logger } from "@aws-sdk/types"; - -/** - * @internal - */ -export class NoOpLogger implements Logger { - public trace() {} - public debug() {} - public info() {} - public warn() {} - public error() {} -} diff --git a/packages/smithy-client/src/client.spec.ts b/packages/smithy-client/src/client.spec.ts deleted file mode 100644 index 56ed3944e005..000000000000 --- a/packages/smithy-client/src/client.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Client } from "./client"; - -describe("SmithyClient", () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const mockHandler = jest.fn((args: any) => Promise.resolve({ output: "foo" })); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const mockResolveMiddleware = jest.fn((args) => mockHandler); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const getCommandWithOutput = (output: string) => ({ - resolveMiddleware: mockResolveMiddleware, - }); - const client = new Client({} as any); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it("should return response promise when only command is supplied", async () => { - expect.assertions(1); - await expect(client.send(getCommandWithOutput("foo") as any)).resolves.toEqual("foo"); - }); - - it("should return response promise when command and options is supplied", async () => { - expect.assertions(3); - const options = { - AbortSignal: "bar", - }; - await expect(client.send(getCommandWithOutput("foo") as any, options)).resolves.toEqual("foo"); - expect(mockResolveMiddleware.mock.calls.length).toEqual(1); - expect(mockResolveMiddleware.mock.calls[0][2 as any]).toEqual(options); - }); - - it("should apply callback when command and callback is supplied", (done) => { - const callback = jest.fn((err, response) => { - expect(response).toEqual("foo"); - done(); - }); - client.send(getCommandWithOutput("foo") as any, callback); - }); - - it("should apply callback when command, options and callback is supplied", (done) => { - const callback = jest.fn((err, response) => { - expect(response).toEqual("foo"); - expect(mockResolveMiddleware.mock.calls.length).toEqual(1); - expect(mockResolveMiddleware.mock.calls[0][2 as any]).toEqual(options); - done(); - }); - const options = { - AbortSignal: "bar", - }; - client.send(getCommandWithOutput("foo") as any, options, callback); - }); -}); diff --git a/packages/smithy-client/src/client.ts b/packages/smithy-client/src/client.ts deleted file mode 100644 index a8be184147a5..000000000000 --- a/packages/smithy-client/src/client.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { constructStack } from "@aws-sdk/middleware-stack"; -import { Client as IClient, Command, MetadataBearer, MiddlewareStack, RequestHandler } from "@aws-sdk/types"; - -/** - * @internal - */ -export interface SmithyConfiguration { - requestHandler: RequestHandler; - /** - * The API version set internally by the SDK, and is - * not planned to be used by customer code. - * @internal - */ - readonly apiVersion: string; -} - -/** - * @internal - */ -export type SmithyResolvedConfiguration = SmithyConfiguration; - -/** - * @public - */ -export class Client< - HandlerOptions, - ClientInput extends object, - ClientOutput extends MetadataBearer, - ResolvedClientConfiguration extends SmithyResolvedConfiguration -> implements IClient -{ - public middlewareStack: MiddlewareStack = constructStack(); - readonly config: ResolvedClientConfiguration; - constructor(config: ResolvedClientConfiguration) { - this.config = config; - } - send( - command: Command>, - options?: HandlerOptions - ): Promise; - send( - command: Command>, - cb: (err: any, data?: OutputType) => void - ): void; - send( - command: Command>, - options: HandlerOptions, - cb: (err: any, data?: OutputType) => void - ): void; - send( - command: Command>, - optionsOrCb?: HandlerOptions | ((err: any, data?: OutputType) => void), - cb?: (err: any, data?: OutputType) => void - ): Promise | void { - const options = typeof optionsOrCb !== "function" ? optionsOrCb : undefined; - const callback = typeof optionsOrCb === "function" ? (optionsOrCb as (err: any, data?: OutputType) => void) : cb; - const handler = command.resolveMiddleware(this.middlewareStack as any, this.config, options); - if (callback) { - handler(command) - .then( - (result) => callback(null, result.output), - (err: any) => callback(err) - ) - .catch( - // prevent any errors thrown in the callback from triggering an - // unhandled promise rejection - () => {} - ); - } else { - return handler(command).then((result) => result.output); - } - } - - destroy() { - if (this.config.requestHandler.destroy) this.config.requestHandler.destroy(); - } -} diff --git a/packages/smithy-client/src/collect-stream-body.spec.ts b/packages/smithy-client/src/collect-stream-body.spec.ts deleted file mode 100644 index 18755a8fb9f4..000000000000 --- a/packages/smithy-client/src/collect-stream-body.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Uint8ArrayBlobAdapter } from "@aws-sdk/util-stream"; - -import { collectBody } from "./collect-stream-body"; - -describe(collectBody.name, () => { - it("passes through Uint8Array", async () => { - const body = new Uint8Array(); - const arr = await collectBody(body, { - async streamCollector(stream: any) { - return new Uint8Array(stream); - }, - }); - - expect(arr).toBe(body); - }); - - it("uses the contextual streamCollector", async () => { - const body = "x"; - const arr = await collectBody(body, { - async streamCollector(stream: any) { - return Uint8ArrayBlobAdapter.fromString(stream); - }, - }); - - expect(arr.transformToString()).toEqual("x"); - }); - - it("uses the contextual streamCollector for empty string", async () => { - const body = ""; - const arr = await collectBody(body, { - async streamCollector(stream: any) { - return Uint8ArrayBlobAdapter.fromString(stream); - }, - }); - - expect(arr.transformToString()).toEqual(""); - }); - - it("defaults to an empty Uint8Array", async () => { - const body = null; - const arr = await collectBody(body, { - async streamCollector(stream: any) { - return Uint8ArrayBlobAdapter.fromString(stream); - }, - }); - - expect(arr.transformToString()).toEqual(""); - }); -}); diff --git a/packages/smithy-client/src/collect-stream-body.ts b/packages/smithy-client/src/collect-stream-body.ts deleted file mode 100644 index 5e8baf49145c..000000000000 --- a/packages/smithy-client/src/collect-stream-body.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Uint8ArrayBlobAdapter } from "@aws-sdk/util-stream"; -import { SerdeContext } from "@smithy/types"; - -/** - * @internal - * - * Collect low-level response body stream to Uint8Array. - */ -export const collectBody = async ( - streamBody: any = new Uint8Array(), - context: { - streamCollector: SerdeContext["streamCollector"]; - } -): Promise => { - if (streamBody instanceof Uint8Array) { - return Uint8ArrayBlobAdapter.mutate(streamBody); - } - - if (!streamBody) { - return Uint8ArrayBlobAdapter.mutate(new Uint8Array()); - } - - const fromContext = context.streamCollector(streamBody); - - return Uint8ArrayBlobAdapter.mutate(await fromContext); -}; diff --git a/packages/smithy-client/src/command.ts b/packages/smithy-client/src/command.ts deleted file mode 100644 index 30fd48cc507c..000000000000 --- a/packages/smithy-client/src/command.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { constructStack } from "@aws-sdk/middleware-stack"; -import { Command as ICommand, Handler, MetadataBearer, MiddlewareStack as IMiddlewareStack } from "@aws-sdk/types"; - -/** - * @public - */ -export abstract class Command< - Input extends ClientInput, - Output extends ClientOutput, - ResolvedClientConfiguration, - ClientInput extends object = any, - ClientOutput extends MetadataBearer = any -> implements ICommand -{ - abstract input: Input; - readonly middlewareStack: IMiddlewareStack = constructStack(); - abstract resolveMiddleware( - stack: IMiddlewareStack, - configuration: ResolvedClientConfiguration, - options: any - ): Handler; -} diff --git a/packages/smithy-client/src/constants.ts b/packages/smithy-client/src/constants.ts deleted file mode 100644 index a67475a338f5..000000000000 --- a/packages/smithy-client/src/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * @internal - */ -export const SENSITIVE_STRING = "***SensitiveInformation***"; diff --git a/packages/smithy-client/src/create-aggregated-client.spec.ts b/packages/smithy-client/src/create-aggregated-client.spec.ts deleted file mode 100644 index b0ea19c6eaea..000000000000 --- a/packages/smithy-client/src/create-aggregated-client.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { createAggregatedClient } from "./create-aggregated-client"; - -class BaseClient { - send = jest.fn() as any; -} -class AggregatedClient extends BaseClient { - constructor(commands) { - super(); - createAggregatedClient(commands, AggregatedClient as any); - } -} -class UserClient extends AggregatedClient {} - -describe(createAggregatedClient.name, () => { - it("extends its base client", async () => { - const commands = { - ActionCommand: jest.fn(), - }; - - const aggregatedClient: any = new AggregatedClient(commands); - - expect(aggregatedClient).toBeInstanceOf(BaseClient); - expect(aggregatedClient).toBeInstanceOf(AggregatedClient); - }); - - it("is extensible", async () => { - const commands = { - ActionCommand: jest.fn(), - }; - - const aggregatedClient: any = new UserClient(commands); - - expect(aggregatedClient).toBeInstanceOf(UserClient); - expect(aggregatedClient).toBeInstanceOf(AggregatedClient); - expect(aggregatedClient).toBeInstanceOf(BaseClient); - }); - - it("should dispatch using the command lookup", async () => { - const commands = { - ActionCommand: jest.fn(), - }; - const aggregatedClient: any = new AggregatedClient(commands); - - expect(() => aggregatedClient.nonExistentMethod()).toThrow(); - expect(() => aggregatedClient.action()).not.toThrow(); - - expect(typeof aggregatedClient.action).toBe("function"); - }); - - it("should call send with the matching command", async () => { - const commands = { - ActionCommand: jest.fn(), - }; - const aggregatedClient: any = new AggregatedClient(commands); - - await aggregatedClient.action({ a: "a" }); - - expect(commands.ActionCommand).toHaveBeenCalledWith({ a: "a" }); - expect(aggregatedClient.send).toHaveBeenCalled(); - }); -}); diff --git a/packages/smithy-client/src/create-aggregated-client.ts b/packages/smithy-client/src/create-aggregated-client.ts deleted file mode 100644 index 4b38851ea68b..000000000000 --- a/packages/smithy-client/src/create-aggregated-client.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Client } from "./client"; - -/** - * @internal - * - * @param commands - command lookup container. - * @param client - client instance on which to add aggregated methods. - * @returns an aggregated client with dynamically created methods. - */ -export const createAggregatedClient = ( - commands: Record, - Client: { new (...args: any): Client } -): void => { - for (const command of Object.keys(commands)) { - const CommandCtor = commands[command]; - const methodImpl = async function (this: InstanceType, args: any, optionsOrCb: any, cb: any) { - const command: any = new CommandCtor(args); - if (typeof optionsOrCb === "function") { - this.send(command, optionsOrCb); - } else if (typeof cb === "function") { - if (typeof optionsOrCb !== "object") throw new Error(`Expected http options but got ${typeof optionsOrCb}`); - this.send(command, optionsOrCb || {}, cb); - } else { - return this.send(command, optionsOrCb); - } - }; - const methodName = (command[0].toLowerCase() + command.slice(1)).replace(/Command$/, ""); - Client.prototype[methodName] = methodImpl; - } -}; diff --git a/packages/smithy-client/src/date-utils.spec.ts b/packages/smithy-client/src/date-utils.spec.ts deleted file mode 100644 index 519f266e884b..000000000000 --- a/packages/smithy-client/src/date-utils.spec.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { - parseEpochTimestamp, - parseRfc3339DateTime, - parseRfc3339DateTimeWithOffset, - parseRfc7231DateTime, -} from "./date-utils"; - -const invalidRfc3339DateTimes = [ - "85-04-12T23:20:50.52Z", - "985-04-12T23:20:50.52Z", - "1985-13-12T23:20:50.52Z", - "1985-00-12T23:20:50.52Z", - "1985-4-12T23:20:50.52Z", - "1985-04-32T23:20:50.52Z", - "1985-04-00T23:20:50.52Z", - "1985-04-05T24:20:50.52Z", - "1985-04-05T23:61:50.52Z", - "1985-04-05T23:20:61.52Z", - "1985-04-31T23:20:50.52Z", - "2005-02-29T15:59:59Z", - "1996-12-19T16:39:57", - "Mon, 31 Dec 1990 15:59:60 GMT", - "Monday, 31-Dec-90 15:59:60 GMT", - "Mon Dec 31 15:59:60 1990", - "1985-04-12T23:20:50.52Z1985-04-12T23:20:50.52Z", - "1985-04-12T23:20:50.52ZA", - "A1985-04-12T23:20:50.52Z", -]; - -describe("parseRfc3339DateTime", () => { - it.each([null, undefined])("returns undefined for %s", (value) => { - expect(parseRfc3339DateTime(value)).toBeUndefined(); - }); - - describe("parses properly formatted dates", () => { - it("with fractional seconds", () => { - expect(parseRfc3339DateTime("1985-04-12T23:20:50.52Z")).toEqual(new Date(Date.UTC(1985, 3, 12, 23, 20, 50, 520))); - }); - it("without fractional seconds", () => { - expect(parseRfc3339DateTime("1985-04-12T23:20:50Z")).toEqual(new Date(Date.UTC(1985, 3, 12, 23, 20, 50, 0))); - }); - it("with leap seconds", () => { - expect(parseRfc3339DateTime("1990-12-31T15:59:60Z")).toEqual(new Date(Date.UTC(1990, 11, 31, 15, 59, 60, 0))); - }); - it("with leap days", () => { - expect(parseRfc3339DateTime("2004-02-29T15:59:59Z")).toEqual(new Date(Date.UTC(2004, 1, 29, 15, 59, 59, 0))); - }); - it("with leading zeroes", () => { - expect(parseRfc3339DateTime("0004-02-09T05:09:09.09Z")).toEqual(new Date(Date.UTC(4, 1, 9, 5, 9, 9, 90))); - expect(parseRfc3339DateTime("0004-02-09T00:00:00.00Z")).toEqual(new Date(Date.UTC(4, 1, 9, 0, 0, 0, 0))); - }); - }); - - it.each(invalidRfc3339DateTimes)("rejects %s", (value) => { - expect(() => parseRfc3339DateTime(value)).toThrowError(); - }); - - // parseRfc3339DateTime throws on offsets. parseRfc3339DateTimeWithOffset can handle these. - it.each(["2019-12-16T22:48:18+02:04", "2019-12-16T22:48:18-01:02"])("rejects %s", (value) => { - expect(() => parseRfc3339DateTime(value)).toThrowError(); - }); -}); - -describe("parseRfc3339DateTimeWithOffset", () => { - it.each([null, undefined])("returns undefined for %s", (value) => { - expect(parseRfc3339DateTime(value)).toBeUndefined(); - }); - - describe("parses properly formatted dates", () => { - it("with fractional seconds", () => { - expect(parseRfc3339DateTimeWithOffset("1985-04-12T23:20:50.52Z")).toEqual( - new Date(Date.UTC(1985, 3, 12, 23, 20, 50, 520)) - ); - }); - it("without fractional seconds", () => { - expect(parseRfc3339DateTimeWithOffset("1985-04-12T23:20:50Z")).toEqual( - new Date(Date.UTC(1985, 3, 12, 23, 20, 50, 0)) - ); - }); - it("with leap seconds", () => { - expect(parseRfc3339DateTimeWithOffset("1990-12-31T15:59:60Z")).toEqual( - new Date(Date.UTC(1990, 11, 31, 15, 59, 60, 0)) - ); - }); - it("with leap days", () => { - expect(parseRfc3339DateTimeWithOffset("2004-02-29T15:59:59Z")).toEqual( - new Date(Date.UTC(2004, 1, 29, 15, 59, 59, 0)) - ); - }); - it("with leading zeroes", () => { - expect(parseRfc3339DateTimeWithOffset("0004-02-09T05:09:09.09Z")).toEqual( - new Date(Date.UTC(4, 1, 9, 5, 9, 9, 90)) - ); - expect(parseRfc3339DateTimeWithOffset("0004-02-09T00:00:00.00Z")).toEqual( - new Date(Date.UTC(4, 1, 9, 0, 0, 0, 0)) - ); - }); - it("with negative offset", () => { - expect(parseRfc3339DateTimeWithOffset("2019-12-16T22:48:18-01:02")).toEqual( - new Date(Date.UTC(2019, 11, 16, 23, 50, 18, 0)) - ); - }); - it("with positive offset", () => { - expect(parseRfc3339DateTimeWithOffset("2019-12-16T22:48:18+02:04")).toEqual( - new Date(Date.UTC(2019, 11, 16, 20, 44, 18, 0)) - ); - }); - }); - - it.each(invalidRfc3339DateTimes)("rejects %s", (value) => { - expect(() => parseRfc3339DateTimeWithOffset(value)).toThrowError(); - }); -}); - -describe("parseRfc7231DateTime", () => { - it.each([null, undefined])("returns undefined for %s", (value) => { - expect(parseRfc7231DateTime(value)).toBeUndefined(); - }); - - describe("parses properly formatted dates", () => { - describe("with fractional seconds", () => { - it.each([ - ["imf-fixdate", "Sun, 06 Nov 1994 08:49:37.52 GMT"], - ["rfc-850", "Sunday, 06-Nov-94 08:49:37.52 GMT"], - ["asctime", "Sun Nov 6 08:49:37.52 1994"], - ])("in format %s", (_, value) => { - expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(1994, 10, 6, 8, 49, 37, 520))); - }); - }); - describe("with fractional seconds - single digit hour", () => { - it.each([ - ["imf-fixdate", "Sun, 06 Nov 1994 8:49:37.52 GMT"], - ["rfc-850", "Sunday, 06-Nov-94 8:49:37.52 GMT"], - ["asctime", "Sun Nov 6 8:49:37.52 1994"], - ])("in format %s", (_, value) => { - expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(1994, 10, 6, 8, 49, 37, 520))); - }); - }); - describe("without fractional seconds", () => { - it.each([ - ["imf-fixdate", "Sun, 06 Nov 1994 08:49:37 GMT"], - ["rfc-850", "Sunday, 06-Nov-94 08:49:37 GMT"], - ["asctime", "Sun Nov 6 08:49:37 1994"], - ])("in format %s", (_, value) => { - expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(1994, 10, 6, 8, 49, 37, 0))); - }); - }); - describe("without fractional seconds - single digit hour", () => { - it.each([ - ["imf-fixdate", "Sun, 06 Nov 1994 8:49:37 GMT"], - ["rfc-850", "Sunday, 06-Nov-94 8:49:37 GMT"], - ["asctime", "Sun Nov 6 8:49:37 1994"], - ])("in format %s", (_, value) => { - expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(1994, 10, 6, 8, 49, 37, 0))); - }); - }); - describe("with leap seconds", () => { - it.each([ - ["imf-fixdate", "Mon, 31 Dec 1990 15:59:60 GMT"], - ["rfc-850", "Monday, 31-Dec-90 15:59:60 GMT"], - ["asctime", "Mon Dec 31 15:59:60 1990"], - ])("in format %s", (_, value) => { - expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(1990, 11, 31, 15, 59, 60, 0))); - }); - }); - describe("with leap seconds - single digit hour", () => { - it.each([ - ["imf-fixdate", "Mon, 31 Dec 1990 8:59:60 GMT"], - ["rfc-850", "Monday, 31-Dec-90 8:59:60 GMT"], - ["asctime", "Mon Dec 31 8:59:60 1990"], - ])("in format %s", (_, value) => { - expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(1990, 11, 31, 8, 59, 60, 0))); - }); - }); - describe("with leap days", () => { - it.each([ - ["imf-fixdate", "Sun, 29 Feb 2004 15:59:59 GMT"], - ["rfc-850", "Sunday, 29-Feb-04 15:59:59 GMT"], - ["asctime", "Sun Feb 29 15:59:59 2004"], - ])("in format %s", (_, value) => { - expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(2004, 1, 29, 15, 59, 59, 0))); - }); - }); - describe("with leap days - single digit hour", () => { - it.each([ - ["imf-fixdate", "Sun, 29 Feb 2004 8:59:59 GMT"], - ["rfc-850", "Sunday, 29-Feb-04 8:59:59 GMT"], - ["asctime", "Sun Feb 29 8:59:59 2004"], - ])("in format %s", (_, value) => { - expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(2004, 1, 29, 8, 59, 59, 0))); - }); - }); - describe("with leading zeroes", () => { - it.each([ - ["imf-fixdate", "Sun, 06 Nov 0004 08:09:07.02 GMT", 4], - ["rfc-850", "Sunday, 06-Nov-04 08:09:07.02 GMT", 2004], - ["asctime", "Sun Nov 6 08:09:07.02 0004", 4], - ])("in format %s", (_, value, year) => { - expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(year, 10, 6, 8, 9, 7, 20))); - }); - }); - describe("with all-zero components", () => { - it.each([ - ["imf-fixdate", "Sun, 06 Nov 0004 00:00:00.00 GMT", 4], - ["rfc-850", "Sunday, 06-Nov-04 00:00:00.00 GMT", 2004], - ["asctime", "Sun Nov 6 00:00:00.00 0004", 4], - ])("in format %s", (_, value, year) => { - expect(parseRfc7231DateTime(value)).toEqual(new Date(Date.UTC(year, 10, 6, 0, 0, 0, 0))); - }); - }); - }); - - describe("when parsing rfc-850 dates", () => { - it("properly adjusts 2-digit years", () => { - // These tests will fail in a couple of decades. Good luck future developers. - expect(parseRfc7231DateTime("Friday, 31-Dec-99 12:34:56.789 GMT")).toEqual( - new Date(Date.UTC(1999, 11, 31, 12, 34, 56, 789)) - ); - expect(parseRfc7231DateTime("Thursday, 31-Dec-65 12:34:56.789 GMT")).toEqual( - new Date(Date.UTC(2065, 11, 31, 12, 34, 56, 789)) - ); - }); - }); - - it.each([ - "1985-04-12T23:20:50.52Z", - "1985-04-12T23:20:50Z", - - "Sun, 06 Nov 0004 08:09:07.02 GMTSun, 06 Nov 0004 08:09:07.02 GMT", - "Sun, 06 Nov 0004 08:09:07.02 GMTA", - "ASun, 06 Nov 0004 08:09:07.02 GMT", - "Sun, 06 Nov 94 08:49:37 GMT", - "Sun, 06 Dov 1994 08:49:37 GMT", - "Mun, 06 Nov 1994 08:49:37 GMT", - "Sunday, 06 Nov 1994 08:49:37 GMT", - "Sun, 06 November 1994 08:49:37 GMT", - "Sun, 06 Nov 1994 24:49:37 GMT", - "Sun, 06 Nov 1994 08:69:37 GMT", - "Sun, 06 Nov 1994 08:49:67 GMT", - "Sun, 06-11-1994 08:49:37 GMT", - "Sun, 06 11 1994 08:49:37 GMT", - "Sun, 31 Nov 1994 08:49:37 GMT", - "Sun, 29 Feb 2005 15:59:59 GMT", - - "Sunday, 06-Nov-04 08:09:07.02 GMTSunday, 06-Nov-04 08:09:07.02 GMT", - "ASunday, 06-Nov-04 08:09:07.02 GMT", - "Sunday, 06-Nov-04 08:09:07.02 GMTA", - "Sunday, 06-Nov-1994 08:49:37 GMT", - "Sunday, 06-Dov-94 08:49:37 GMT", - "Sundae, 06-Nov-94 08:49:37 GMT", - "Sun, 06-Nov-94 08:49:37 GMT", - "Sunday, 06-November-94 08:49:37 GMT", - "Sunday, 06-Nov-94 24:49:37 GMT", - "Sunday, 06-Nov-94 08:69:37 GMT", - "Sunday, 06-Nov-94 08:49:67 GMT", - "Sunday, 06 11 94 08:49:37 GMT", - "Sunday, 06-11-1994 08:49:37 GMT", - "Sunday, 31-Nov-94 08:49:37 GMT", - "Sunday, 29-Feb-05 15:59:59 GMT", - - "Sun Nov 6 08:09:07.02 0004Sun Nov 6 08:09:07.02 0004", - "ASun Nov 6 08:09:07.02 0004", - "Sun Nov 6 08:09:07.02 0004A", - "Sun Nov 6 08:49:37 94", - "Sun Dov 6 08:49:37 1994", - "Mun Nov 6 08:49:37 1994", - "Sunday Nov 6 08:49:37 1994", - "Sun November 6 08:49:37 1994", - "Sun Nov 6 24:49:37 1994", - "Sun Nov 6 08:69:37 1994", - "Sun Nov 6 08:49:67 1994", - "Sun 06-11 08:49:37 1994", - "Sun 06 11 08:49:37 1994", - "Sun 11 6 08:49:37 1994", - "Sun Nov 31 08:49:37 1994", - "Sun Feb 29 15:59:59 2005", - "Sun Nov 6 08:49:37 1994", - ])("rejects %s", (value) => { - expect(() => parseRfc7231DateTime(value)).toThrowError(); - }); -}); - -describe("parseEpochTimestamp", () => { - it.each([null, undefined])("returns undefined for %s", (value) => { - expect(parseEpochTimestamp(value)).toBeUndefined(); - }); - - describe("parses properly formatted dates", () => { - describe("with fractional seconds", () => { - it.each(["482196050.52", 482196050.52])("parses %s", (value) => { - expect(parseEpochTimestamp(value)).toEqual(new Date(Date.UTC(1985, 3, 12, 23, 20, 50, 520))); - }); - }); - describe("without fractional seconds", () => { - it.each(["482196050", 482196050, 482196050.0])("parses %s", (value) => { - expect(parseEpochTimestamp(value)).toEqual(new Date(Date.UTC(1985, 3, 12, 23, 20, 50, 0))); - }); - }); - }); - it.each([ - "1985-04-12T23:20:50.52Z", - "1985-04-12T23:20:50Z", - "Mon, 31 Dec 1990 15:59:60 GMT", - "Monday, 31-Dec-90 15:59:60 GMT", - "Mon Dec 31 15:59:60 1990", - "NaN", - NaN, - "Infinity", - Infinity, - "0x42", - ])("rejects %s", (value) => { - expect(() => parseEpochTimestamp(value)).toThrowError(); - }); -}); diff --git a/packages/smithy-client/src/date-utils.ts b/packages/smithy-client/src/date-utils.ts deleted file mode 100644 index c8d52a1f4d1c..000000000000 --- a/packages/smithy-client/src/date-utils.ts +++ /dev/null @@ -1,392 +0,0 @@ -import { strictParseByte, strictParseDouble, strictParseFloat32, strictParseShort } from "./parse-utils"; - -// Build indexes outside so we allocate them once. -const DAYS: Array = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - -// These must be kept in order -// prettier-ignore -const MONTHS: Array = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - -/** - * @internal - * - * Builds a proper UTC HttpDate timestamp from a Date object - * since not all environments will have this as the expected - * format. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString} - * - Prior to ECMAScript 2018, the format of the return value - * - varied according to the platform. The most common return - * - value was an RFC-1123 formatted date stamp, which is a - * - slightly updated version of RFC-822 date stamps. - */ -export function dateToUtcString(date: Date): string { - const year = date.getUTCFullYear(); - const month = date.getUTCMonth(); - const dayOfWeek = date.getUTCDay(); - const dayOfMonthInt = date.getUTCDate(); - const hoursInt = date.getUTCHours(); - const minutesInt = date.getUTCMinutes(); - const secondsInt = date.getUTCSeconds(); - - // Build 0 prefixed strings for contents that need to be - // two digits and where we get an integer back. - const dayOfMonthString = dayOfMonthInt < 10 ? `0${dayOfMonthInt}` : `${dayOfMonthInt}`; - const hoursString = hoursInt < 10 ? `0${hoursInt}` : `${hoursInt}`; - const minutesString = minutesInt < 10 ? `0${minutesInt}` : `${minutesInt}`; - const secondsString = secondsInt < 10 ? `0${secondsInt}` : `${secondsInt}`; - - return `${DAYS[dayOfWeek]}, ${dayOfMonthString} ${MONTHS[month]} ${year} ${hoursString}:${minutesString}:${secondsString} GMT`; -} - -const RFC3339 = new RegExp(/^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?[zZ]$/); - -/** - * @internal - * - * Parses a value into a Date. Returns undefined if the input is null or - * undefined, throws an error if the input is not a string that can be parsed - * as an RFC 3339 date. - * - * Input strings must conform to RFC3339 section 5.6, and cannot have a UTC - * offset. Fractional precision is supported. - * - * @see {@link https://xml2rfc.tools.ietf.org/public/rfc/html/rfc3339.html#anchor14} - * - * @param value - the value to parse - * @returns a Date or undefined - */ -export const parseRfc3339DateTime = (value: unknown): Date | undefined => { - if (value === null || value === undefined) { - return undefined; - } - if (typeof value !== "string") { - throw new TypeError("RFC-3339 date-times must be expressed as strings"); - } - const match = RFC3339.exec(value); - if (!match) { - throw new TypeError("Invalid RFC-3339 date-time value"); - } - - const [_, yearStr, monthStr, dayStr, hours, minutes, seconds, fractionalMilliseconds] = match; - - const year = strictParseShort(stripLeadingZeroes(yearStr))!; - const month = parseDateValue(monthStr, "month", 1, 12); - const day = parseDateValue(dayStr, "day", 1, 31); - - return buildDate(year, month, day, { hours, minutes, seconds, fractionalMilliseconds }); -}; - -const RFC3339_WITH_OFFSET = new RegExp( - /^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(([-+]\d{2}\:\d{2})|[zZ])$/ -); - -/** - * @internal - * - * Parses a value into a Date. Returns undefined if the input is null or - * undefined, throws an error if the input is not a string that can be parsed - * as an RFC 3339 date. - * - * Input strings must conform to RFC3339 section 5.6, and can have a UTC - * offset. Fractional precision is supported. - * - * @see {@link https://xml2rfc.tools.ietf.org/public/rfc/html/rfc3339.html#anchor14} - * - * @param value - the value to parse - * @returns a Date or undefined - */ -export const parseRfc3339DateTimeWithOffset = (value: unknown): Date | undefined => { - if (value === null || value === undefined) { - return undefined; - } - if (typeof value !== "string") { - throw new TypeError("RFC-3339 date-times must be expressed as strings"); - } - const match = RFC3339_WITH_OFFSET.exec(value); - if (!match) { - throw new TypeError("Invalid RFC-3339 date-time value"); - } - - const [_, yearStr, monthStr, dayStr, hours, minutes, seconds, fractionalMilliseconds, offsetStr] = match; - - const year = strictParseShort(stripLeadingZeroes(yearStr))!; - const month = parseDateValue(monthStr, "month", 1, 12); - const day = parseDateValue(dayStr, "day", 1, 31); - const date = buildDate(year, month, day, { hours, minutes, seconds, fractionalMilliseconds }); - - // The final regex capture group is either an offset, or "z". If it is not a "z", - // attempt to parse the offset and adjust the date. - if (offsetStr.toUpperCase() != "Z") { - date.setTime(date.getTime() - parseOffsetToMilliseconds(offsetStr)); - } - return date; -}; - -const IMF_FIXDATE = new RegExp( - /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\d{2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d{1,2}):(\d{2}):(\d{2})(?:\.(\d+))? GMT$/ -); -const RFC_850_DATE = new RegExp( - /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\d{2})-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d{2}) (\d{1,2}):(\d{2}):(\d{2})(?:\.(\d+))? GMT$/ -); -const ASC_TIME = new RegExp( - /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( [1-9]|\d{2}) (\d{1,2}):(\d{2}):(\d{2})(?:\.(\d+))? (\d{4})$/ -); - -/** - * @internal - * - * Parses a value into a Date. Returns undefined if the input is null or - * undefined, throws an error if the input is not a string that can be parsed - * as an RFC 7231 IMF-fixdate or obs-date. - * - * Input strings must conform to RFC7231 section 7.1.1.1. Fractional seconds are supported. - * - * @see {@link https://datatracker.ietf.org/doc/html/rfc7231.html#section-7.1.1.1} - * - * @param value - the value to parse - * @returns a Date or undefined - */ -export const parseRfc7231DateTime = (value: unknown): Date | undefined => { - if (value === null || value === undefined) { - return undefined; - } - if (typeof value !== "string") { - throw new TypeError("RFC-7231 date-times must be expressed as strings"); - } - - let match = IMF_FIXDATE.exec(value); - if (match) { - const [_, dayStr, monthStr, yearStr, hours, minutes, seconds, fractionalMilliseconds] = match; - return buildDate( - strictParseShort(stripLeadingZeroes(yearStr))!, - parseMonthByShortName(monthStr), - parseDateValue(dayStr, "day", 1, 31), - { hours, minutes, seconds, fractionalMilliseconds } - ); - } - - match = RFC_850_DATE.exec(value); - if (match) { - const [_, dayStr, monthStr, yearStr, hours, minutes, seconds, fractionalMilliseconds] = match; - // RFC 850 dates use 2-digit years. So we parse the year specifically, - // and then once we've constructed the entire date, we adjust it if the resultant date - // is too far in the future. - return adjustRfc850Year( - buildDate(parseTwoDigitYear(yearStr), parseMonthByShortName(monthStr), parseDateValue(dayStr, "day", 1, 31), { - hours, - minutes, - seconds, - fractionalMilliseconds, - }) - ); - } - - match = ASC_TIME.exec(value); - if (match) { - const [_, monthStr, dayStr, hours, minutes, seconds, fractionalMilliseconds, yearStr] = match; - return buildDate( - strictParseShort(stripLeadingZeroes(yearStr))!, - parseMonthByShortName(monthStr), - parseDateValue(dayStr.trimLeft(), "day", 1, 31), - { hours, minutes, seconds, fractionalMilliseconds } - ); - } - - throw new TypeError("Invalid RFC-7231 date-time value"); -}; - -/** - * @internal - * - * Parses a value into a Date. Returns undefined if the input is null or - * undefined, throws an error if the input is not a number or a parseable string. - * - * Input strings must be an integer or floating point number. Fractional seconds are supported. - * - * @param value - the value to parse - * @returns a Date or undefined - */ -export const parseEpochTimestamp = (value: unknown): Date | undefined => { - if (value === null || value === undefined) { - return undefined; - } - - let valueAsDouble: number; - if (typeof value === "number") { - valueAsDouble = value; - } else if (typeof value === "string") { - valueAsDouble = strictParseDouble(value)!; - } else { - throw new TypeError("Epoch timestamps must be expressed as floating point numbers or their string representation"); - } - - if (Number.isNaN(valueAsDouble) || valueAsDouble === Infinity || valueAsDouble === -Infinity) { - throw new TypeError("Epoch timestamps must be valid, non-Infinite, non-NaN numerics"); - } - return new Date(Math.round(valueAsDouble * 1000)); -}; - -interface RawTime { - hours: string; - minutes: string; - seconds: string; - fractionalMilliseconds: string | undefined; -} - -/** - * Build a date from a numeric year, month, date, and an match with named groups - * "H", "m", s", and "frac", representing hours, minutes, seconds, and optional fractional seconds. - * @param year - numeric year - * @param month - numeric month, 1-indexed - * @param day - numeric year - * @param match - match with groups "H", "m", s", and "frac" - */ -const buildDate = (year: number, month: number, day: number, time: RawTime): Date => { - const adjustedMonth = month - 1; // JavaScript, and our internal data structures, expect 0-indexed months - validateDayOfMonth(year, adjustedMonth, day); - // Adjust month down by 1 - return new Date( - Date.UTC( - year, - adjustedMonth, - day, - parseDateValue(time.hours, "hour", 0, 23), - parseDateValue(time.minutes, "minute", 0, 59), - // seconds can go up to 60 for leap seconds - parseDateValue(time.seconds, "seconds", 0, 60), - parseMilliseconds(time.fractionalMilliseconds) - ) - ); -}; - -/** - * RFC 850 dates use a 2-digit year; start with the assumption that if it doesn't - * match the current year, then it's a date in the future, then let adjustRfc850Year adjust - * the final date back to the past if it's too far in the future. - * - * Example: in 2021, start with the assumption that '11' is '2111', and that '22' is '2022'. - * adjustRfc850Year will adjust '11' to 2011, (as 2111 is more than 50 years in the future), - * but keep '22' as 2022. in 2099, '11' will represent '2111', but '98' should be '2098'. - * There's no description of an RFC 850 date being considered too far in the past in RFC-7231, - * so it's entirely possible that 2011 is a valid interpretation of '11' in 2099. - * @param value - the 2 digit year to parse - * @returns number a year that is equal to or greater than the current UTC year - */ -const parseTwoDigitYear = (value: string): number => { - const thisYear = new Date().getUTCFullYear(); - const valueInThisCentury = Math.floor(thisYear / 100) * 100 + strictParseShort(stripLeadingZeroes(value))!; - if (valueInThisCentury < thisYear) { - // This may end up returning a year that adjustRfc850Year turns back by 100. - // That's fine! We don't know the other components of the date yet, so there are - // boundary conditions that only adjustRfc850Year can handle. - return valueInThisCentury + 100; - } - return valueInThisCentury; -}; - -const FIFTY_YEARS_IN_MILLIS = 50 * 365 * 24 * 60 * 60 * 1000; - -/** - * Adjusts the year value found in RFC 850 dates according to the rules - * expressed in RFC7231, which state: - * - *

Recipients of a timestamp value in rfc850-date format, which uses a - * two-digit year, MUST interpret a timestamp that appears to be more - * than 50 years in the future as representing the most recent year in - * the past that had the same last two digits.
- * - * @param input - a Date that assumes the two-digit year was in the future - * @returns a Date that is in the past if input is \> 50 years in the future - */ -const adjustRfc850Year = (input: Date): Date => { - if (input.getTime() - new Date().getTime() > FIFTY_YEARS_IN_MILLIS) { - return new Date( - Date.UTC( - input.getUTCFullYear() - 100, - input.getUTCMonth(), - input.getUTCDate(), - input.getUTCHours(), - input.getUTCMinutes(), - input.getUTCSeconds(), - input.getUTCMilliseconds() - ) - ); - } - return input; -}; - -const parseMonthByShortName = (value: string): number => { - const monthIdx = MONTHS.indexOf(value); - if (monthIdx < 0) { - throw new TypeError(`Invalid month: ${value}`); - } - return monthIdx + 1; -}; - -const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - -/** - * Validate the day is valid for the given month. - * @param year - the year - * @param month - the month (0-indexed) - * @param day - the day of the month - */ -const validateDayOfMonth = (year: number, month: number, day: number) => { - let maxDays = DAYS_IN_MONTH[month]; - if (month === 1 && isLeapYear(year)) { - maxDays = 29; - } - - if (day > maxDays) { - throw new TypeError(`Invalid day for ${MONTHS[month]} in ${year}: ${day}`); - } -}; - -const isLeapYear = (year: number): boolean => { - return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); -}; - -const parseDateValue = (value: string, type: string, lower: number, upper: number): number => { - const dateVal = strictParseByte(stripLeadingZeroes(value))!; - if (dateVal < lower || dateVal > upper) { - throw new TypeError(`${type} must be between ${lower} and ${upper}, inclusive`); - } - return dateVal; -}; - -const parseMilliseconds = (value: string | undefined): number => { - if (value === null || value === undefined) { - return 0; - } - - return strictParseFloat32("0." + value)! * 1000; -}; - -// Parses offset string and returns offset in milliseconds. -const parseOffsetToMilliseconds = (value: string): number => { - const directionStr = value[0]; - let direction = 1; - if (directionStr == "+") { - direction = 1; - } else if (directionStr == "-") { - direction = -1; - } else { - throw new TypeError(`Offset direction, ${directionStr}, must be "+" or "-"`); - } - - const hour = Number(value.substring(1, 3)); - const minute = Number(value.substring(4, 6)); - return direction * (hour * 60 + minute) * 60 * 1000; -}; - -const stripLeadingZeroes = (value: string): string => { - let idx = 0; - while (idx < value.length - 1 && value.charAt(idx) === "0") { - idx++; - } - if (idx === 0) { - return value; - } - return value.slice(idx); -}; diff --git a/packages/smithy-client/src/default-error-handler.ts b/packages/smithy-client/src/default-error-handler.ts deleted file mode 100644 index cf742aa24eba..000000000000 --- a/packages/smithy-client/src/default-error-handler.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { HttpResponse, ResponseMetadata } from "@aws-sdk/types"; - -import { decorateServiceException } from "./exceptions"; - -/** - * Always throws an error with the given `exceptionCtor` and other arguments. - * This is only called from an error handling code path. - * - * @internal - */ -export const throwDefaultError = ({ output, parsedBody, exceptionCtor, errorCode }: any) => { - const $metadata = deserializeMetadata(output); - const statusCode = $metadata.httpStatusCode ? $metadata.httpStatusCode + "" : undefined; - const response = new exceptionCtor({ - name: parsedBody?.code || parsedBody?.Code || errorCode || statusCode || "UnknownError", - $fault: "client", - $metadata, - }); - throw decorateServiceException(response, parsedBody); -}; - -/** - * @internal - * - * Creates {@link throwDefaultError} with bound ExceptionCtor. - */ -export const withBaseException = (ExceptionCtor: { new (...args: any): any }): any => { - return ({ output, parsedBody, errorCode }: any) => { - throwDefaultError({ output, parsedBody, exceptionCtor: ExceptionCtor, errorCode }); - }; -}; - -const deserializeMetadata = (output: HttpResponse): ResponseMetadata => ({ - httpStatusCode: output.statusCode, - requestId: - output.headers["x-amzn-requestid"] ?? output.headers["x-amzn-request-id"] ?? output.headers["x-amz-request-id"], - extendedRequestId: output.headers["x-amz-id-2"], - cfId: output.headers["x-amz-cf-id"], -}); diff --git a/packages/smithy-client/src/defaults-mode.ts b/packages/smithy-client/src/defaults-mode.ts deleted file mode 100644 index 13767bf095e3..000000000000 --- a/packages/smithy-client/src/defaults-mode.ts +++ /dev/null @@ -1,57 +0,0 @@ -// smithy-typescript generated code -/** - * @internal - */ -export const loadConfigsForDefaultMode = (mode: ResolvedDefaultsMode): DefaultsModeConfigs => { - switch (mode) { - case "standard": - return { - retryMode: "standard", - connectionTimeout: 3100, - }; - case "in-region": - return { - retryMode: "standard", - connectionTimeout: 1100, - }; - case "cross-region": - return { - retryMode: "standard", - connectionTimeout: 3100, - }; - case "mobile": - return { - retryMode: "standard", - connectionTimeout: 30000, - }; - default: - return {}; - } -}; - -/** - * Option determining how certain default configuration options are resolved in the SDK. It can be one of the value listed below: - * * `"standard"`:

The STANDARD mode provides the latest recommended default values that should be safe to run in most scenarios

Note that the default values vended from this mode might change as best practices may evolve. As a result, it is encouraged to perform tests when upgrading the SDK

- * * `"in-region"`:

The IN_REGION mode builds on the standard mode and includes optimization tailored for applications which call AWS services from within the same AWS region

Note that the default values vended from this mode might change as best practices may evolve. As a result, it is encouraged to perform tests when upgrading the SDK

- * * `"cross-region"`:

The CROSS_REGION mode builds on the standard mode and includes optimization tailored for applications which call AWS services in a different region

Note that the default values vended from this mode might change as best practices may evolve. As a result, it is encouraged to perform tests when upgrading the SDK

- * * `"mobile"`:

The MOBILE mode builds on the standard mode and includes optimization tailored for mobile applications

Note that the default values vended from this mode might change as best practices may evolve. As a result, it is encouraged to perform tests when upgrading the SDK

- * * `"auto"`:

The AUTO mode is an experimental mode that builds on the standard mode. The SDK will attempt to discover the execution environment to determine the appropriate settings automatically.

Note that the auto detection is heuristics-based and does not guarantee 100% accuracy. STANDARD mode will be used if the execution environment cannot be determined. The auto detection might query EC2 Instance Metadata service, which might introduce latency. Therefore we recommend choosing an explicit defaults_mode instead if startup latency is critical to your application

- * * `"legacy"`:

The LEGACY mode provides default settings that vary per SDK and were used prior to establishment of defaults_mode

- * - * @default "legacy" - */ -export type DefaultsMode = "standard" | "in-region" | "cross-region" | "mobile" | "auto" | "legacy"; - -/** - * @internal - */ -export type ResolvedDefaultsMode = Exclude; - -/** - * @internal - */ -export interface DefaultsModeConfigs { - retryMode?: string; - connectionTimeout?: number; - requestTimeout?: number; -} diff --git a/packages/smithy-client/src/emitWarningIfUnsupportedVersion.spec.ts b/packages/smithy-client/src/emitWarningIfUnsupportedVersion.spec.ts deleted file mode 100644 index c65db5f4adf0..000000000000 --- a/packages/smithy-client/src/emitWarningIfUnsupportedVersion.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -describe.skip("emitWarningIfUnsupportedVersion", () => { - let emitWarningIfUnsupportedVersion; - const emitWarning = process.emitWarning; - const supportedVersion = "14.0.0"; - - beforeEach(() => { - const module = require("./emitWarningIfUnsupportedVersion"); - emitWarningIfUnsupportedVersion = module.emitWarningIfUnsupportedVersion; - }); - - afterEach(() => { - jest.clearAllMocks(); - jest.resetModules(); - process.emitWarning = emitWarning; - }); - - describe(`emits warning for Node.js <${supportedVersion}`, () => { - const getPreviousMajorVersion = (major: number) => (major === 0 ? 0 : major - 1); - - const getPreviousMinorVersion = ([major, minor]: [number, number]) => - minor === 0 ? [getPreviousMajorVersion(major), 9] : [major, minor - 1]; - - const getPreviousPatchVersion = ([major, minor, patch]: [number, number, number]) => - patch === 0 ? [...getPreviousMinorVersion([major, minor]), 9] : [major, minor, patch - 1]; - - const [major, minor, patch] = supportedVersion.split(".").map(Number); - it.each( - [ - getPreviousPatchVersion([major, minor, patch]), - [...getPreviousMinorVersion([major, minor]), 0], - [getPreviousMajorVersion(major), 0, 0], - ].map((arr) => `v${arr.join(".")}`) - )(`%s`, async (unsupportedVersion) => { - process.emitWarning = jest.fn(); - emitWarningIfUnsupportedVersion(unsupportedVersion); - - // Verify that the warning was emitted. - expect(process.emitWarning).toHaveBeenCalledTimes(1); - expect(process.emitWarning).toHaveBeenCalledWith( - `The AWS SDK for JavaScript (v3) will\n` + - `no longer support Node.js ${unsupportedVersion} on November 1, 2022.\n\n` + - `To continue receiving updates to AWS services, bug fixes, and security\n` + - `updates please upgrade to Node.js 14.x or later.\n\n` + - `For details, please refer our blog post: https://a.co/48dbdYz`, - `NodeDeprecationWarning` - ); - - // Verify that the warning emits only once. - emitWarningIfUnsupportedVersion(unsupportedVersion); - expect(process.emitWarning).toHaveBeenCalledTimes(1); - }); - }); - - describe(`emits no warning for Node.js >=${supportedVersion}`, () => { - const [major, minor, patch] = supportedVersion.split(".").map(Number); - it.each( - [ - [major, minor, patch], - [major, minor, patch + 1], - [major, minor + 1, 0], - [major + 1, 0, 0], - ].map((arr) => `v${arr.join(".")}`) - )(`%s`, async (unsupportedVersion) => { - process.emitWarning = jest.fn(); - emitWarningIfUnsupportedVersion(unsupportedVersion); - expect(process.emitWarning).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/packages/smithy-client/src/emitWarningIfUnsupportedVersion.ts b/packages/smithy-client/src/emitWarningIfUnsupportedVersion.ts deleted file mode 100644 index ec6b96baf34b..000000000000 --- a/packages/smithy-client/src/emitWarningIfUnsupportedVersion.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Stores whether the warning was already emitted. -let warningEmitted = false; - -/** - * @internal - * - * Emits warning if the provided Node.js version string is pending deprecation. - * - * @param version - The Node.js version string. - */ -export const emitWarningIfUnsupportedVersion = (version: string) => { - if (version && !warningEmitted && parseInt(version.substring(1, version.indexOf("."))) < 14) { - warningEmitted = true; - // ToDo: Turn back warning for future Node.js version deprecation - // process.emitWarning( - // `The AWS SDK for JavaScript (v3) will\n` + - // `no longer support Node.js ${version} on November 1, 2022.\n\n` + - // `To continue receiving updates to AWS services, bug fixes, and security\n` + - // `updates please upgrade to Node.js 14.x or later.\n\n` + - // `For details, please refer our blog post: https://a.co/48dbdYz`, - // `NodeDeprecationWarning` - // ); - } -}; diff --git a/packages/smithy-client/src/exceptions.spec.ts b/packages/smithy-client/src/exceptions.spec.ts deleted file mode 100644 index 89eb66734fdf..000000000000 --- a/packages/smithy-client/src/exceptions.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { decorateServiceException, ExceptionOptionType, ServiceException } from "./exceptions"; - -it("ServiceException extends from Error", () => { - expect( - new ServiceException({ - name: "Error", - message: "", - $fault: "client", - $metadata: {}, - }) - ).toBeInstanceOf(Error); -}); - -it("ExceptionOptionType allows specifying message", () => { - class SomeException extends ServiceException { - readonly code: string; - constructor(opts: ExceptionOptionType) { - super({ - name: "SomeException", - $fault: "client", - ...opts, - }); - this.code = opts.code; - } - } - const exception = new SomeException({ - message: "message", - code: "code", - $metadata: {}, - }); - expect(exception.message).toBe("message"); - expect(exception.code).toBe("code"); -}); - -describe("decorateServiceException", () => { - const exception = new ServiceException({ - name: "Error", - message: "Error", - $fault: "client", - $metadata: {}, - }); - - it("should inject unmodeled members to the exception", () => { - const decorated = decorateServiceException(exception, { foo: "foo" }); - expect((decorated as any).foo).toBe("foo"); - }); - - it("should not inject unmodeled members to the undefined", () => { - const decorated = decorateServiceException(exception, { message: undefined }); - expect(decorated.message).toBe("Error"); - }); - - it("should not overwrite the parsed exceptions", () => { - const decorated = decorateServiceException(exception, { message: "Another Error" }); - expect(decorated.message).toBe("Error"); - }); - - it("should replace Message with message", () => { - const decorated = decorateServiceException({ - name: "Error", - Message: "message", - } as any); - expect(decorated.message).toBe("message"); - }); -}); diff --git a/packages/smithy-client/src/exceptions.ts b/packages/smithy-client/src/exceptions.ts deleted file mode 100644 index 5d7b898045ae..000000000000 --- a/packages/smithy-client/src/exceptions.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { HttpResponse, MetadataBearer, ResponseMetadata, RetryableTrait, SmithyException } from "@aws-sdk/types"; - -/** - * The type of the exception class constructor parameter. The returned type contains the properties - * in the `ExceptionType` but not in the `BaseExceptionType`. If the `BaseExceptionType` contains - * `$metadata` and `message` properties, it's also included in the returned type. - * @internal - */ -export type ExceptionOptionType = Omit< - ExceptionType, - Exclude ->; - -/** - * @public - */ -export interface ServiceExceptionOptions extends SmithyException, MetadataBearer { - message?: string; -} - -/** - * @public - * - * Base exception class for the exceptions from the server-side. - */ -export class ServiceException extends Error implements SmithyException, MetadataBearer { - readonly $fault: "client" | "server"; - - $response?: HttpResponse; - $retryable?: RetryableTrait; - $metadata: ResponseMetadata; - - constructor(options: ServiceExceptionOptions) { - super(options.message); - Object.setPrototypeOf(this, ServiceException.prototype); - this.name = options.name; - this.$fault = options.$fault; - this.$metadata = options.$metadata; - } -} - -/** - * This method inject unmodeled member to a deserialized SDK exception, - * and load the error message from different possible keys('message', - * 'Message'). - * - * @internal - */ -export const decorateServiceException = ( - exception: E, - additions: Record = {} -): E => { - // apply additional properties to deserialized ServiceException object - Object.entries(additions) - .filter(([, v]) => v !== undefined) - .forEach(([k, v]) => { - // @ts-ignore examine unmodeled keys - if (exception[k] == undefined || exception[k] === "") { - // @ts-ignore assign unmodeled keys - exception[k] = v; - } - }); - // load error message from possible locations - // @ts-expect-error message could exist in Message key. - const message = exception.message || exception.Message || "UnknownError"; - exception.message = message; - // @ts-expect-error - delete exception.Message; - return exception; -}; diff --git a/packages/smithy-client/src/extended-encode-uri-component.spec.ts b/packages/smithy-client/src/extended-encode-uri-component.spec.ts deleted file mode 100644 index ab21a39e6fff..000000000000 --- a/packages/smithy-client/src/extended-encode-uri-component.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { extendedEncodeURIComponent } from "./extended-encode-uri-component"; - -describe(extendedEncodeURIComponent.name, () => { - const encodedValues: [string, string][] = [ - ["!", "%21"], - ["'", "%27"], - ["(", "%28"], - [")", "%29"], - ["*", "%2A"], - ]; - - const verify = (table: [string, string][]) => { - it.each(table)(`encodes %s as %s`, (input, output) => { - expect(extendedEncodeURIComponent(input)).toStrictEqual(output); - }); - }; - - verify(encodedValues); - verify([encodedValues.reduce((acc, [input, output]) => [acc[0].concat(input), acc[1].concat(output)], ["", ""])]); -}); diff --git a/packages/smithy-client/src/extended-encode-uri-component.ts b/packages/smithy-client/src/extended-encode-uri-component.ts deleted file mode 100644 index 367d35b087e4..000000000000 --- a/packages/smithy-client/src/extended-encode-uri-component.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @internal - * - * Function that wraps encodeURIComponent to encode additional characters - * to fully adhere to RFC 3986. - */ -export function extendedEncodeURIComponent(str: string): string { - return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { - return "%" + c.charCodeAt(0).toString(16).toUpperCase(); - }); -} diff --git a/packages/smithy-client/src/get-array-if-single-item.ts b/packages/smithy-client/src/get-array-if-single-item.ts deleted file mode 100644 index 05c6153abf1e..000000000000 --- a/packages/smithy-client/src/get-array-if-single-item.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @internal - * - * The XML parser will set one K:V for a member that could - * return multiple entries but only has one. - */ -export const getArrayIfSingleItem = (mayBeArray: T): T | T[] => - Array.isArray(mayBeArray) ? mayBeArray : [mayBeArray]; diff --git a/packages/smithy-client/src/get-value-from-text-node.spec.ts b/packages/smithy-client/src/get-value-from-text-node.spec.ts deleted file mode 100644 index 49d96dfdca57..000000000000 --- a/packages/smithy-client/src/get-value-from-text-node.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { getValueFromTextNode } from "./get-value-from-text-node"; - -describe("getValueFromTextNode", () => { - const valueInsideTextNode = "valueInsideTextNode"; - - it("doesn't modify object if #text is absent", () => { - const input = { - key: "value", - keyObj: { - keyInsideObj: "valueInsideObj", - }, - }; - const output = getValueFromTextNode(input); - expect(output).toBe(input); - }); - - it("populates key with value in #text at first level", () => { - const input = { - key: "value", - keyWithoutTextNode: { - key: "value", - }, - keyWithTextNode: { - "#text": valueInsideTextNode, - }, - }; - const output = getValueFromTextNode(input); - expect(output.key).toBe(input.key); - expect(output.keyWithoutTextNode).toBe(input.keyWithoutTextNode); - expect(output.keyWithTextNode).toBe(valueInsideTextNode); - }); - - it("populates key with value in #text at second level", () => { - const input = { - key: "value", - keyWithoutTextNodeAtAnyLevel: { - keyObj: { - key: "value", - }, - }, - keyWithTextNodeAtLevel2: { - keyWithTextNode: { - "#text": valueInsideTextNode, - }, - }, - }; - const output = getValueFromTextNode(input); - expect(output.key).toBe(input.key); - expect(output.keyWithoutTextNodeAtAnyLevel).toBe(input.keyWithoutTextNodeAtAnyLevel); - expect(output.keyWithTextNodeAtLevel2.keyWithTextNode).toBe(valueInsideTextNode); - }); -}); diff --git a/packages/smithy-client/src/get-value-from-text-node.ts b/packages/smithy-client/src/get-value-from-text-node.ts deleted file mode 100644 index 485792520cd0..000000000000 --- a/packages/smithy-client/src/get-value-from-text-node.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @internal - * - * Recursively parses object and populates value is node from - * "#text" key if it's available - */ -export const getValueFromTextNode = (obj: any) => { - const textNodeName = "#text"; - for (const key in obj) { - if (obj.hasOwnProperty(key) && obj[key][textNodeName] !== undefined) { - obj[key] = obj[key][textNodeName]; - } else if (typeof obj[key] === "object" && obj[key] !== null) { - obj[key] = getValueFromTextNode(obj[key]); - } - } - return obj; -}; diff --git a/packages/smithy-client/src/index.ts b/packages/smithy-client/src/index.ts index 5f5538bddd29..95a0e81b0806 100644 --- a/packages/smithy-client/src/index.ts +++ b/packages/smithy-client/src/index.ts @@ -1,22 +1 @@ -export * from "./NoOpLogger"; -export * from "./client"; -export * from "./collect-stream-body"; -export * from "./command"; -export * from "./constants"; -export * from "./create-aggregated-client"; -export * from "./date-utils"; -export * from "./default-error-handler"; -export * from "./defaults-mode"; -export * from "./emitWarningIfUnsupportedVersion"; -export * from "./exceptions"; -export * from "./extended-encode-uri-component"; -export * from "./get-array-if-single-item"; -export * from "./get-value-from-text-node"; -export * from "./lazy-json"; -export * from "./object-mapping"; -export * from "./parse-utils"; -export * from "./resolve-path"; -export * from "./ser-utils"; -export * from "./serde-json"; -export * from "./split-every"; -export type { DocumentType, SdkError, SmithyException } from "@aws-sdk/types"; +export * from "@smithy/smithy-client"; diff --git a/packages/smithy-client/src/lazy-json.spec.ts b/packages/smithy-client/src/lazy-json.spec.ts deleted file mode 100644 index 7d1ef59d6acb..000000000000 --- a/packages/smithy-client/src/lazy-json.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { LazyJsonString } from "./lazy-json"; -describe("LazyJsonString", () => { - it("should has string methods", () => { - const jsonValue = new LazyJsonString('"foo"'); - expect(jsonValue.length).toBe(5); - expect(jsonValue.toString()).toBe('"foo"'); - }); - - it("should deserialize json properly", () => { - const jsonValue = new LazyJsonString('"foo"'); - expect(jsonValue.deserializeJSON()).toBe("foo"); - const wrongJsonValue = new LazyJsonString("foo"); - expect(() => wrongJsonValue.deserializeJSON()).toThrow(); - }); - - it("should get JSON string properly", () => { - const jsonValue = new LazyJsonString('{"foo", "bar"}'); - expect(jsonValue.toJSON()).toBe('{"foo", "bar"}'); - }); - - it("can instantiate from LazyJsonString class", () => { - const original = new LazyJsonString('"foo"'); - const newOne = LazyJsonString.fromObject(original); - expect(newOne.toString()).toBe('"foo"'); - }); - - it("can instantiate from String class", () => { - const jsonValue = LazyJsonString.fromObject(new String('"foo"')); - expect(jsonValue.toString()).toBe('"foo"'); - }); - - it("can instantiate from object", () => { - const jsonValue = LazyJsonString.fromObject({ foo: "bar" }); - expect(jsonValue.toString()).toBe('{"foo":"bar"}'); - }); -}); diff --git a/packages/smithy-client/src/lazy-json.ts b/packages/smithy-client/src/lazy-json.ts deleted file mode 100644 index 6a9d4b416a9b..000000000000 --- a/packages/smithy-client/src/lazy-json.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Lazy String holder for JSON typed contents. - */ - -interface StringWrapper { - new (arg: any): String; -} - -/** - * Because of https://github.com/microsoft/tslib/issues/95, - * TS 'extends' shim doesn't support extending native types like String. - * So here we create StringWrapper that duplicate everything from String - * class including its prototype chain. So we can extend from here. - * - * @internal - */ -// @ts-ignore StringWrapper implementation is not a simple constructor -export const StringWrapper: StringWrapper = function () { - //@ts-ignore 'this' cannot be assigned to any, but Object.getPrototypeOf accepts any - const Class = Object.getPrototypeOf(this).constructor; - const Constructor = Function.bind.apply(String, [null as any, ...arguments]); - //@ts-ignore Call wrapped String constructor directly, don't bother typing it. - const instance = new Constructor(); - Object.setPrototypeOf(instance, Class.prototype); - return instance as String; -}; -StringWrapper.prototype = Object.create(String.prototype, { - constructor: { - value: StringWrapper, - enumerable: false, - writable: true, - configurable: true, - }, -}); -Object.setPrototypeOf(StringWrapper, String); - -/** - * @internal - */ -export class LazyJsonString extends StringWrapper { - deserializeJSON(): any { - return JSON.parse(super.toString()); - } - - toJSON(): string { - return super.toString(); - } - - static fromObject(object: any): LazyJsonString { - if (object instanceof LazyJsonString) { - return object; - } else if (object instanceof String || typeof object === "string") { - return new LazyJsonString(object); - } - return new LazyJsonString(JSON.stringify(object)); - } -} diff --git a/packages/smithy-client/src/object-mapping.spec.ts b/packages/smithy-client/src/object-mapping.spec.ts deleted file mode 100644 index 3ddede93e0ff..000000000000 --- a/packages/smithy-client/src/object-mapping.spec.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { map, ObjectMappingInstructions, SourceMappingInstructions, take } from "./object-mapping"; - -describe("object mapping", () => { - const example: ObjectMappingInstructions = { - lazyValue1: [, () => 1], - lazyValue2: [, () => 2], - lazyValue3: [, () => 3], - lazyConditionalValue1: [() => true, () => 4], - lazyConditionalValue2: [() => true, () => 5], - lazyConditionalValue3: [true, () => 6], - lazyConditionalValue4: [false, () => 44], - lazyConditionalValue5: [() => false, () => 55], - lazyConditionalValue6: ["", () => 66], - simpleValue1: [, 7], - simpleValue2: [, 8], - simpleValue3: [, 9], - conditionalValue1: [() => true, 10], - conditionalValue2: [() => true, 11], - conditionalValue3: [{}, 12], - conditionalValue4: [false, 110], - conditionalValue5: [() => false, 121], - conditionalValue6: ["", 132], - }; - - const exampleResult: Record = { - lazyValue1: 1, - lazyValue2: 2, - lazyValue3: 3, - lazyConditionalValue1: 4, - lazyConditionalValue2: 5, - lazyConditionalValue3: 6, - simpleValue1: 7, - simpleValue2: 8, - simpleValue3: 9, - conditionalValue1: 10, - conditionalValue2: 11, - conditionalValue3: 12, - }; - - describe("map function", () => { - it("should map various values according to their instruction sets", () => { - expect(map({}, example)).toEqual(exampleResult); - }); - - it("should allow default empty object as target", () => { - expect(map(example)).toEqual(exampleResult); - }); - - it("should allow a uniform default filter to be specified", () => { - expect(map({}, (_: number) => _ % 2 === 0, exampleResult)).toEqual({ - lazyValue2: 2, - lazyConditionalValue1: 4, - lazyConditionalValue3: 6, - simpleValue2: 8, - conditionalValue1: 10, - conditionalValue3: 12, - }); - }); - - it("should allow a set of passing filters", () => { - expect( - map({ - a: [, 0], - b: [, false], - c: [, ""], - d: [, []], - e: [, [void 0, void 0]], - f: [, {}], - g: [, [false, void 0]], - h: [true, void 0], - i: [1, void 0], - j: [" ", void 0], - k: [[], void 0], - l: [{}, void 0], - m: [() => true, void 0], - n: [(val) => val === void 0, void 1], - o: [() => true, () => void 0], - p: [(val) => val !== 1, () => 1], // value is not provided to filter fn when value provider is lazy - q: 0, - r: false, - s: "", - t: undefined, - u: null, - }) - ).toEqual({ - a: 0, - b: false, - c: "", - d: [], - e: [undefined, undefined], - f: {}, - g: [false, undefined], - h: undefined, - i: undefined, - j: undefined, - k: undefined, - l: undefined, - m: undefined, - n: undefined, - o: undefined, - p: 1, - q: 0, - r: false, - s: "", - t: undefined, - u: null, - }); - }); - - it("should block a set of failing filters", () => { - expect( - map({ - a: [, undefined], - b: [, null], - c: [(_) => _ !== "", ""], - d: [(_) => _.length !== 0, []], - e: [0, 0], - f: [false, false], - g: ["", ""], - h: [undefined, undefined], - i: [null, null], - j: [() => false, void 0], - k: [(val) => val !== void 0, void 0], - l: [() => false, () => void 0], - m: [(val) => val === 1, () => 1], // value is not provided to filter fn when value provider is lazy - n: [, () => undefined], - }) - ).toEqual({}); - }); - }); - - describe("take function", () => { - it("will not apply instructions to missing fields", () => { - const input = { - filteredDefault: null, - filteredSupplier: undefined, - filteredMapper: void 0, - filteredFilter: 43, - filteredMapperOnly: null, - } as const; - - const output = {} as const; - - const instructions: SourceMappingInstructions = { - default: [], - filteredDefault: [], - supplier: [, () => "x"], - filteredSupplier: [, () => "x"], - mapper: [, (_) => _ + "x"], - filteredMapper: [, (_) => _ + "x"], - filter: [(_) => _ === 42], - filteredFilter: [(_) => _ === 42], - sourceKey: [, , "SOURCE_KEY"], - sourceKey2: [, (_) => "mapped" + _, "SOURCE_KEY2"], - mapperOnly: (_) => _ + "Only", - filteredMapperOnly: (_) => _ + "Only", - }; - - expect(take(input, instructions)).toEqual(output); - }); - - it("should allow a filter function or value", () => { - const input = { - a: 1, - b: 1, - c: 1, - d: 1, - e: 1, - f: 1, - } as const; - - const output = { - a: 1, - b: 1, - e: 1, - } as const; - - const instructions: SourceMappingInstructions = { - a: [true], - b: [1], - c: [false, () => 1], - d: [0, () => 1], - e: [(_) => _ == 1], - f: [(_) => _ == 2], - }; - - expect(take(input, instructions)).toEqual(output); - }); - - it("should take keys with optional filters and optional mappers", () => { - const input = { - default: 0, - filteredDefault: null, - supplier: false, - filteredSupplier: undefined, - mapper: "y", - filteredMapper: void 0, - filter: 42, - filteredFilter: 43, - SOURCE_KEY: "SOURCE_VALUE", - SOURCE_KEY2: "SOURCE_VALUE2", - mapperOnly: "mapper", - filteredMapperOnly: null, - } as const; - - const output = { - default: 0, - supplier: "x", - mapper: "yx", - filter: 42, - sourceKey: "SOURCE_VALUE", - sourceKey2: "mappedSOURCE_VALUE2", - mapperOnly: "mapperOnly", - } as const; - - const instructions: SourceMappingInstructions = { - default: [], - filteredDefault: [], - supplier: [, () => "x"], - filteredSupplier: [, () => "x"], - mapper: [, (_) => _ + "x"], - filteredMapper: [, (_) => _ + "x"], - filter: [(_) => _ === 42], - filteredFilter: [(_) => _ === 42], - sourceKey: [, , "SOURCE_KEY"], - sourceKey2: [, (_) => "mapped" + _, "SOURCE_KEY2"], - mapperOnly: (_) => _ + "Only", - filteredMapperOnly: (_) => _ + "Only", - }; - - expect(take(input, instructions)).toEqual(output); - }); - }); -}); diff --git a/packages/smithy-client/src/object-mapping.ts b/packages/smithy-client/src/object-mapping.ts deleted file mode 100644 index 6c9bc0b61b08..000000000000 --- a/packages/smithy-client/src/object-mapping.ts +++ /dev/null @@ -1,323 +0,0 @@ -/** - * @internal - * - * A set of instructions for multiple keys. - * The aim is to provide a concise yet readable way to map and filter values - * onto a target object. - * - * @example - * ```javascript - * const example: ObjectMappingInstructions = { - * lazyValue1: [, () => 1], - * lazyValue2: [, () => 2], - * lazyValue3: [, () => 3], - * lazyConditionalValue1: [() => true, () => 4], - * lazyConditionalValue2: [() => true, () => 5], - * lazyConditionalValue3: [true, () => 6], - * lazyConditionalValue4: [false, () => 44], - * lazyConditionalValue5: [() => false, () => 55], - * lazyConditionalValue6: ["", () => 66], - * simpleValue1: [, 7], - * simpleValue2: [, 8], - * simpleValue3: [, 9], - * conditionalValue1: [() => true, 10], - * conditionalValue2: [() => true, 11], - * conditionalValue3: [{}, 12], - * conditionalValue4: [false, 110], - * conditionalValue5: [() => false, 121], - * conditionalValue6: ["", 132], - * }; - * - * const exampleResult: Record = { - * lazyValue1: 1, - * lazyValue2: 2, - * lazyValue3: 3, - * lazyConditionalValue1: 4, - * lazyConditionalValue2: 5, - * lazyConditionalValue3: 6, - * simpleValue1: 7, - * simpleValue2: 8, - * simpleValue3: 9, - * conditionalValue1: 10, - * conditionalValue2: 11, - * conditionalValue3: 12, - * }; - * ``` - */ -export type ObjectMappingInstructions = Record; - -/** - * @internal - * - * A variant of the object mapping instruction for the `take` function. - * In this case, the source value is provided to the value function, turning it - * from a supplier into a mapper. - */ -export type SourceMappingInstructions = Record; - -/** - * @internal - * - * An instruction set for assigning a value to a target object. - */ -export type ObjectMappingInstruction = - | LazyValueInstruction - | ConditionalLazyValueInstruction - | SimpleValueInstruction - | ConditionalValueInstruction - | UnfilteredValue; - -/** - * @internal - * - * non-array - */ -export type UnfilteredValue = any; -/** - * @internal - */ -export type LazyValueInstruction = [FilterStatus, ValueSupplier]; -/** - * @internal - */ -export type ConditionalLazyValueInstruction = [FilterStatusSupplier, ValueSupplier]; -/** - * @internal - */ -export type SimpleValueInstruction = [FilterStatus, Value]; -/** - * @internal - */ -export type ConditionalValueInstruction = [ValueFilteringFunction, Value]; -/** - * @internal - */ -export type SourceMappingInstruction = [(ValueFilteringFunction | FilterStatus)?, ValueMapper?, string?]; - -/** - * @internal - * - * Filter is considered passed if - * 1. It is a boolean true. - * 2. It is not undefined and is itself truthy. - * 3. It is undefined and the corresponding _value_ is neither null nor undefined. - */ -export type FilterStatus = boolean | unknown | void; - -/** - * @internal - * - * Supplies the filter check but not against any value as input. - */ -export type FilterStatusSupplier = () => boolean; - -/** - * @internal - * - * Filter check with the given value. - */ -export type ValueFilteringFunction = (value: any) => boolean; - -/** - * @internal - * - * Supplies the value for lazy evaluation. - */ -export type ValueSupplier = () => any; - -/** - * @internal - * - * A function that maps the source value to the target value. - * Defaults to pass-through with nullish check. - */ -export type ValueMapper = (value: any) => any; - -/** - * @internal - * - * A non-function value. - */ -export type Value = any; - -/** - * @internal - * Internal/Private, for codegen use only. - * - * Transfer a set of keys from [instructions] to [target]. - * - * For each instruction in the record, the target key will be the instruction key. - * The target assignment will be conditional on the instruction's filter. - * The target assigned value will be supplied by the instructions as an evaluable function or non-function value. - * - * @see ObjectMappingInstructions for an example. - */ -export function map( - target: any, - filter: (value: any) => boolean, - instructions: Record -): typeof target; -/** - * @internal - */ -export function map(instructions: ObjectMappingInstructions): any; -/** - * @internal - */ -export function map(target: any, instructions: ObjectMappingInstructions): typeof target; -/** - * @internal - */ -export function map(arg0: any, arg1?: any, arg2?: any): any { - let target: any; - let filter: (value?: any) => boolean; - let instructions: ObjectMappingInstructions; - - if (typeof arg1 === "undefined" && typeof arg2 === "undefined") { - target = {}; - instructions = arg0; - } else { - target = arg0; - if (typeof arg1 === "function") { - filter = arg1; - instructions = arg2; - return mapWithFilter(target, filter, instructions); - } else { - instructions = arg1; - } - } - - for (const key of Object.keys(instructions)) { - if (!Array.isArray(instructions[key])) { - target[key] = instructions[key]; // unchecked value. - continue; - } - applyInstruction(target, null, instructions, key); - } - return target; -} - -/** - * Convert a regular object `{ k: v }` to `{ k: [, v] }` mapping instruction set with default - * filter. - * - * @internal - */ -export const convertMap = (target: any): Record => { - const output: Record = {}; - for (const [k, v] of Object.entries(target || {})) { - output[k] = [, v]; - } - return output; -}; - -/** - * @param source - original object with data. - * @param instructions - how to map the data. - * @returns new object mapped from the source object. - * @internal - */ -export const take = (source: any, instructions: SourceMappingInstructions): any => { - const out = {}; - for (const key in instructions) { - applyInstruction(out, source, instructions, key); - } - return out; -}; - -/** - * Private, for codegen use only. - * - * @param target - target object. - * @param filter - uniform filter function to apply to all values - * @param instructions - map of keys and values/suppliers (will be evaluated) - * - * @internal - */ -const mapWithFilter = ( - target: any, - filter: (value: any) => boolean, - instructions: Record -): typeof target => { - return map( - target, - Object.entries(instructions).reduce( - ( - _instructions: ObjectMappingInstructions, - [key, value]: [string, ValueSupplier | Value | ObjectMappingInstruction] - ) => { - if (Array.isArray(value)) { - // is custom instruction and not a value or value supplier - _instructions[key] = value as ObjectMappingInstruction; - } else { - if (typeof value === "function") { - _instructions[key] = [filter, value()]; - } else { - _instructions[key] = [filter, value]; - } - } - - return _instructions; - }, - {} - ) - ); -}; - -/** - * @internal - * - * Applies a single instruction at the given key from source to target. - */ -const applyInstruction = ( - target: any, - source: null | any, - instructions: ObjectMappingInstructions | Record, - targetKey: string -): void => { - if (source !== null) { - let instruction = instructions[targetKey]; - if (typeof instruction === "function") { - instruction = [, instruction]; - } - const [filter = nonNullish, valueFn = pass, sourceKey = targetKey] = instruction; - if ((typeof filter === "function" && filter(source[sourceKey])) || (typeof filter !== "function" && !!filter)) { - target[targetKey] = valueFn(source[sourceKey]); - } - return; - } - - // eslint-disable-next-line prefer-const - let [filter, value]: [((_?: any) => boolean) | unknown, any] = instructions[targetKey]; - - if (typeof value === "function") { - let _value: any; - const defaultFilterPassed = filter === undefined && (_value = value()) != null; - const customFilterPassed = - (typeof filter === "function" && !!filter(void 0)) || (typeof filter !== "function" && !!filter); - - if (defaultFilterPassed) { - target[targetKey] = _value; - } else if (customFilterPassed) { - target[targetKey] = value(); - } - } else { - const defaultFilterPassed = filter === undefined && value != null; - const customFilterPassed = - (typeof filter === "function" && !!filter(value)) || (typeof filter !== "function" && !!filter); - - if (defaultFilterPassed || customFilterPassed) { - target[targetKey] = value; - } - } -}; - -/** - * internal - */ -const nonNullish = (_: any) => _ != null; - -/** - * internal - */ -const pass = (_: any) => _; diff --git a/packages/smithy-client/src/parse-utils.spec.ts b/packages/smithy-client/src/parse-utils.spec.ts deleted file mode 100644 index 2e9edac1e53a..000000000000 --- a/packages/smithy-client/src/parse-utils.spec.ts +++ /dev/null @@ -1,790 +0,0 @@ -import { - expectByte, - expectFloat32, - expectInt32, - expectLong, - expectNonNull, - expectObject, - expectShort, - expectUnion, - limitedParseDouble, - limitedParseFloat32, - logger, - parseBoolean, - strictParseByte, - strictParseDouble, - strictParseFloat32, - strictParseInt32, - strictParseLong, - strictParseShort, -} from "./parse-utils"; -import { expectBoolean, expectNumber, expectString } from "./parse-utils"; - -describe("parseBoolean", () => { - it('Returns true for "true"', () => { - expect(parseBoolean("true")).toEqual(true); - }); - - it('Returns false for "false"', () => { - expect(parseBoolean("false")).toEqual(false); - }); - - describe("Throws an error on invalid input", () => { - it.each([ - // These are valid booleans in YAML - "y", - "Y", - "yes", - "Yes", - "YES", - "n", - "N", - "no", - "No", - "NO", - "True", - "TRUE", - "False", - "FALSE", - "on", - "On", - "ON", - "off", - "Off", - "OFF", - // These would be resolve to false using Boolean - 0, - null, - "", - false, - // These would resolve to true using Boolean - true, - "Su Lin", - [], - {}, - ])("rejects %s", (value) => { - expect(() => parseBoolean(value as any)).toThrowError(); - }); - }); -}); - -describe("expectBoolean", () => { - it.each([true, false])("accepts %s", (value) => { - expect(expectBoolean(value)).toEqual(value); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(expectBoolean(value)).toEqual(undefined); - }); - - describe("reluctantly", () => { - let consoleMock: jest.SpyInstance; - beforeEach(() => { - consoleMock = jest.spyOn(logger, "warn").mockImplementation(); - }); - - afterEach(() => { - consoleMock.mockRestore(); - }); - - it.each([1, "true", "True"])("accepts %s", (value) => { - expect(expectBoolean(value)).toEqual(true); - expect(logger.warn).toHaveBeenCalled(); - }); - - it.each([0, "false", "False"])("accepts %s", (value) => { - expect(expectBoolean(value)).toEqual(false); - expect(logger.warn).toHaveBeenCalled(); - }); - }); - - describe("rejects non-booleans", () => { - it.each([1.1, Infinity, -Infinity, NaN, {}, []])("rejects %s", (value) => { - expect(() => expectBoolean(value)).toThrowError(); - }); - }); -}); - -describe("expectNumber", () => { - describe("accepts numbers", () => { - it.each([1, 1.1, Infinity, -Infinity])("accepts %s", (value) => { - expect(expectNumber(value)).toEqual(value); - }); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(expectNumber(value)).toEqual(undefined); - }); - - describe("reluctantly", () => { - let consoleMock: jest.SpyInstance; - beforeEach(() => { - consoleMock = jest.spyOn(logger, "warn").mockImplementation(); - }); - - afterEach(() => { - consoleMock.mockRestore(); - }); - - it.each(["-0", "-1.15", "-1e-5", "1", "1.1", "Infinity", "-Infinity"])("accepts string: %s", (value) => { - expect(expectNumber(value)).toEqual(parseFloat(value)); - }); - - it.each(["-0abcd", "-1.15abcd", "-1e-5abcd", "1abcd", "1.1abcd", "Infinityabcd", "-Infinityabcd"])( - "accepts string: %s", - (value) => { - expect(expectNumber(value)).toEqual(parseFloat(value)); - expect(logger.warn).toHaveBeenCalled(); - } - ); - }); - - describe("rejects non-numbers", () => { - it.each(["NaN", true, false, [], {}])("rejects %s", (value) => { - expect(() => expectNumber(value)).toThrowError(); - }); - }); -}); - -describe("expectFloat32", () => { - describe("accepts numbers", () => { - it.each([ - 1, - 1.1, - Infinity, - -Infinity, - // Smallest positive subnormal number - 2 ** -149, - // Largest subnormal number - 2 ** -126 * (1 - 2 ** -23), - // Smallest positive normal number - 2 ** -126, - // Largest normal number - 2 ** 127 * (2 - 2 ** -23), - // Largest number less than one - 1 - 2 ** -24, - // Smallest number larger than one - 1 + 2 ** -23, - ])("accepts %s", (value) => { - expect(expectNumber(value)).toEqual(value); - }); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(expectNumber(value)).toEqual(undefined); - }); - - describe("rejects non-numbers", () => { - it.each([true, false, [], {}])("rejects %s", (value) => { - expect(() => expectNumber(value)).toThrowError(); - }); - }); - - describe("rejects doubles", () => { - it.each([2 ** 128, -(2 ** 128)])("rejects %s", (value) => { - expect(() => expectFloat32(value)).toThrowError(); - }); - }); -}); - -describe("expectLong", () => { - describe("accepts 64-bit integers", () => { - it.each([ - 1, - Number.MAX_SAFE_INTEGER, - Number.MIN_SAFE_INTEGER, - 2 ** 31 - 1, - -(2 ** 31), - 2 ** 15 - 1, - -(2 ** 15), - 127, - -128, - ])("accepts %s", (value) => { - expect(expectLong(value)).toEqual(value); - }); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(expectLong(value)).toEqual(undefined); - }); - - describe("rejects non-integers", () => { - it.each([1.1, "1", "1.1", NaN, true, [], {}])("rejects %s", (value) => { - expect(() => expectLong(value)).toThrowError(); - }); - }); -}); - -describe("expectInt32", () => { - describe("accepts 32-bit integers", () => { - it.each([1, 2 ** 31 - 1, -(2 ** 31), 2 ** 15 - 1, -(2 ** 15), 127, -128])("accepts %s", (value) => { - expect(expectInt32(value)).toEqual(value); - }); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(expectInt32(value)).toEqual(undefined); - }); - - describe("rejects non-integers", () => { - it.each([ - 1.1, - "1", - "1.1", - NaN, - true, - [], - {}, - Number.MAX_SAFE_INTEGER, - Number.MIN_SAFE_INTEGER, - 2 ** 31, - -(2 ** 31 + 1), - ])("rejects %s", (value) => { - expect(() => expectInt32(value)).toThrowError(); - }); - }); -}); - -describe("expectShort", () => { - describe("accepts 16-bit integers", () => { - it.each([1, 2 ** 15 - 1, -(2 ** 15), 127, -128])("accepts %s", (value) => { - expect(expectShort(value)).toEqual(value); - }); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(expectShort(value)).toEqual(undefined); - }); - - describe("rejects non-integers", () => { - it.each([ - 1.1, - "1", - "1.1", - NaN, - true, - [], - {}, - 2 ** 63 - 1, - -(2 ** 63 + 1), - 2 ** 31 - 1, - -(2 ** 31 + 1), - 2 ** 15, - -(2 ** 15 + 1), - ])("rejects %s", (value) => { - expect(() => expectShort(value)).toThrowError(); - }); - }); -}); - -describe("expectByte", () => { - describe("accepts 8-bit integers", () => { - it.each([1, 127, -128])("accepts %s", (value) => { - expect(expectByte(value)).toEqual(value); - }); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(expectByte(value)).toEqual(undefined); - }); - - describe("rejects non-integers", () => { - it.each([ - 1.1, - "1", - "1.1", - NaN, - true, - [], - {}, - Number.MAX_SAFE_INTEGER, - Number.MIN_SAFE_INTEGER, - 2 ** 31 - 1, - -(2 ** 31 + 1), - 2 ** 15 - 1, - -(2 ** 15 + 1), - 128, - -129, - ])("rejects %s", (value) => { - expect(() => expectByte(value)).toThrowError(); - }); - }); -}); - -describe("expectNonNull", () => { - it.each([1, 1.1, "1", NaN, true, [], ["a", 123], { a: 123 }, [{ a: 123 }], "{ a : 123 }", '{"a":123}'])( - "accepts %s", - (value) => { - expect(expectNonNull(value)).toEqual(value); - } - ); - - it.each([null, undefined])("rejects %s", (value) => { - expect(() => expectNonNull(value)).toThrowError(); - }); -}); - -describe("expectObject", () => { - it("accepts objects", () => { - expect(expectObject({ a: 123 })).toEqual({ a: 123 }); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(expectObject(value)).toEqual(undefined); - }); - - describe("rejects non-objects", () => { - it.each([1, 1.1, "1", NaN, true, [], ["a", 123], [{ a: 123 }], "{ a : 123 }", '{"a":123}'])( - "rejects %s", - (value) => { - expect(() => expectObject(value)).toThrowError(); - } - ); - }); -}); - -describe("expectString", () => { - it("accepts strings", () => { - expect(expectString("foo")).toEqual("foo"); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(expectString(value)).toEqual(undefined); - }); - - describe("reluctantly", () => { - let consoleMock: jest.SpyInstance; - beforeEach(() => { - consoleMock = jest.spyOn(logger, "warn").mockImplementation(); - }); - - afterEach(() => { - consoleMock.mockRestore(); - }); - - it.each([1, NaN, Infinity, -Infinity, true, false])("accepts numbers or booleans: %s", (value) => { - expect(expectString(value)).toEqual(String(value)); - expect(logger.warn).toHaveBeenCalled(); - }); - }); - - describe("rejects non-strings", () => { - it.each([[], {}])("rejects %s", (value) => { - expect(() => expectString(value)).toThrowError(); - }); - }); -}); - -describe("expectUnion", () => { - it.each([null, undefined])("accepts %s", (value) => { - expect(expectUnion(value)).toEqual(undefined); - }); - describe("rejects non-objects", () => { - it.each([1, NaN, Infinity, -Infinity, true, false, [], "abc"])("%s", (value) => { - expect(() => expectUnion(value)).toThrowError(); - }); - }); - describe("rejects malformed unions", () => { - it.each([{}, { a: null }, { a: undefined }, { a: 1, b: 2 }])("%s", (value) => { - expect(() => expectUnion(value)).toThrowError(); - }); - }); - describe("accepts unions", () => { - it.each([{ a: 1 }, { a: 1, b: null }])("%s", (value) => { - expect(expectUnion(value)).toEqual(value); - }); - }); -}); - -describe("strictParseDouble", () => { - describe("accepts non-numeric floats as strings", () => { - expect(strictParseDouble("Infinity")).toEqual(Infinity); - expect(strictParseDouble("-Infinity")).toEqual(-Infinity); - expect(strictParseDouble("NaN")).toEqual(NaN); - }); - - describe("rejects implicit NaN", () => { - it.each([ - "foo", - "123ABC", - "ABC123", - "12AB3C", - "1.A", - "1.1A", - "1.1A1", - "0xFF", - "0XFF", - "0b1111", - "0B1111", - "0777", - "0o777", - "0O777", - "1n", - "1N", - "1_000", - "e", - "e1", - ".1", - ])("rejects %s", (value) => { - expect(() => strictParseDouble(value)).toThrowError(); - }); - }); - - it("accepts numeric strings", () => { - expect(strictParseDouble("1")).toEqual(1); - expect(strictParseDouble("-1")).toEqual(-1); - expect(strictParseDouble("1.1")).toEqual(1.1); - expect(strictParseDouble("1e1")).toEqual(10); - expect(strictParseDouble("-1e1")).toEqual(-10); - expect(strictParseDouble("1e+1")).toEqual(10); - expect(strictParseDouble("1e-1")).toEqual(0.1); - expect(strictParseDouble("1E1")).toEqual(10); - expect(strictParseDouble("1E+1")).toEqual(10); - expect(strictParseDouble("1E-1")).toEqual(0.1); - }); - - describe("accepts numbers", () => { - it.each([1, 1.1, Infinity, -Infinity, NaN])("accepts %s", (value) => { - expect(strictParseDouble(value)).toEqual(value); - }); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(strictParseDouble(value)).toEqual(undefined); - }); -}); - -describe("strictParseFloat32", () => { - describe("accepts non-numeric floats as strings", () => { - expect(strictParseFloat32("Infinity")).toEqual(Infinity); - expect(strictParseFloat32("-Infinity")).toEqual(-Infinity); - expect(strictParseFloat32("NaN")).toEqual(NaN); - }); - - describe("rejects implicit NaN", () => { - it.each([ - "foo", - "123ABC", - "ABC123", - "12AB3C", - "1.A", - "1.1A", - "1.1A1", - "0xFF", - "0XFF", - "0b1111", - "0B1111", - "0777", - "0o777", - "0O777", - "1n", - "1N", - "1_000", - "e", - "e1", - ".1", - ])("rejects %s", (value) => { - expect(() => strictParseFloat32(value)).toThrowError(); - }); - }); - - describe("rejects doubles", () => { - it.each([2 ** 128, -(2 ** 128)])("rejects %s", (value) => { - expect(() => strictParseFloat32(value)).toThrowError(); - }); - }); - - it("accepts numeric strings", () => { - expect(strictParseFloat32("1")).toEqual(1); - expect(strictParseFloat32("-1")).toEqual(-1); - expect(strictParseFloat32("1.1")).toEqual(1.1); - expect(strictParseFloat32("1e1")).toEqual(10); - expect(strictParseFloat32("-1e1")).toEqual(-10); - expect(strictParseFloat32("1e+1")).toEqual(10); - expect(strictParseFloat32("1e-1")).toEqual(0.1); - expect(strictParseFloat32("1E1")).toEqual(10); - expect(strictParseFloat32("1E+1")).toEqual(10); - expect(strictParseFloat32("1E-1")).toEqual(0.1); - }); - - describe("accepts numbers", () => { - it.each([1, 1.1, Infinity, -Infinity, NaN])("accepts %s", (value) => { - expect(strictParseFloat32(value)).toEqual(value); - }); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(strictParseFloat32(value)).toEqual(undefined); - }); -}); - -describe("limitedParseDouble", () => { - it("accepts non-numeric floats as strings", () => { - expect(limitedParseDouble("Infinity")).toEqual(Infinity); - expect(limitedParseDouble("-Infinity")).toEqual(-Infinity); - expect(limitedParseDouble("NaN")).toEqual(NaN); - }); - - it("rejects implicit NaN", () => { - expect(() => limitedParseDouble("foo")).toThrowError(); - }); - - describe("rejects numeric strings", () => { - it.each(["1", "1.1"])("rejects %s", (value) => { - expect(() => limitedParseDouble(value)).toThrowError(); - }); - }); - - describe("accepts numbers", () => { - it.each([ - 1, - 1.1, - Infinity, - -Infinity, - NaN, - // Smallest positive subnormal number - 2 ** -1074, - // Largest subnormal number - 2 ** -1022 * (1 - 2 ** -52), - // Smallest positive normal number - 2 ** -1022, - // Largest number - 2 ** 1023 * (1 + (1 - 2 ** -52)), - // Largest number less than one - 1 - 2 ** -53, - // Smallest number larger than one - 1 + 2 ** -52, - ])("accepts %s", (value) => { - expect(limitedParseDouble(value)).toEqual(value); - }); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(limitedParseDouble(value)).toEqual(undefined); - }); -}); - -describe("limitedParseFloat32", () => { - it("accepts non-numeric floats as strings", () => { - expect(limitedParseFloat32("Infinity")).toEqual(Infinity); - expect(limitedParseFloat32("-Infinity")).toEqual(-Infinity); - expect(limitedParseFloat32("NaN")).toEqual(NaN); - }); - - it("rejects implicit NaN", () => { - expect(() => limitedParseFloat32("foo")).toThrowError(); - }); - - describe("rejects numeric strings", () => { - it.each(["1", "1.1"])("rejects %s", (value) => { - expect(() => limitedParseFloat32(value)).toThrowError(); - }); - }); - - describe("accepts numbers", () => { - it.each([ - 1, - 1.1, - Infinity, - -Infinity, - NaN, - // Smallest positive subnormal number - 2 ** -149, - // Largest subnormal number - 2 ** -126 * (1 - 2 ** -23), - // Smallest positive normal number - 2 ** -126, - // Largest normal number - 2 ** 127 * (2 - 2 ** -23), - // Largest number less than one - 1 - 2 ** -24, - // Smallest number larger than one - 1 + 2 ** -23, - ])("accepts %s", (value) => { - expect(limitedParseFloat32(value)).toEqual(value); - }); - }); - - describe("rejects doubles", () => { - it.each([2 ** 128, -(2 ** 128)])("rejects %s", (value) => { - expect(() => limitedParseFloat32(value)).toThrowError(); - }); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(limitedParseFloat32(value)).toEqual(undefined); - }); -}); - -describe("strictParseLong", () => { - describe("accepts integers", () => { - describe("accepts 64-bit integers", () => { - it.each([1, 2 ** 63 - 1, -(2 ** 63), 2 ** 31 - 1, -(2 ** 31), 2 ** 15 - 1, -(2 ** 15), 127, -128])( - "accepts %s", - (value) => { - expect(strictParseLong(value)).toEqual(value); - } - ); - }); - expect(strictParseLong("1")).toEqual(1); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(strictParseLong(value)).toEqual(undefined); - }); - - describe("rejects non-integers", () => { - it.each([ - 1.1, - "1.1", - "NaN", - "Infinity", - "-Infinity", - NaN, - Infinity, - -Infinity, - true, - false, - [], - {}, - "foo", - "123ABC", - "ABC123", - "12AB3C", - ])("rejects %s", (value) => { - expect(() => strictParseLong(value as any)).toThrowError(); - }); - }); -}); - -describe("strictParseInt32", () => { - describe("accepts integers", () => { - describe("accepts 32-bit integers", () => { - it.each([1, 2 ** 31 - 1, -(2 ** 31), 2 ** 15 - 1, -(2 ** 15), 127, -128])("accepts %s", (value) => { - expect(strictParseInt32(value)).toEqual(value); - }); - }); - expect(strictParseInt32("1")).toEqual(1); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(strictParseInt32(value)).toEqual(undefined); - }); - - describe("rejects non-integers", () => { - it.each([ - 1.1, - "1.1", - "NaN", - "Infinity", - "-Infinity", - NaN, - Infinity, - -Infinity, - true, - false, - [], - {}, - 2 ** 63 - 1, - -(2 ** 63 + 1), - 2 ** 31, - -(2 ** 31 + 1), - "foo", - "123ABC", - "ABC123", - "12AB3C", - ])("rejects %s", (value) => { - expect(() => strictParseInt32(value as any)).toThrowError(); - }); - }); -}); - -describe("strictParseShort", () => { - describe("accepts integers", () => { - describe("accepts 16-bit integers", () => { - it.each([1, 2 ** 15 - 1, -(2 ** 15), 127, -128])("accepts %s", (value) => { - expect(strictParseShort(value)).toEqual(value); - }); - }); - expect(strictParseShort("1")).toEqual(1); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(strictParseShort(value)).toEqual(undefined); - }); - - describe("rejects non-integers", () => { - it.each([ - 1.1, - "1.1", - "NaN", - "Infinity", - "-Infinity", - NaN, - Infinity, - -Infinity, - true, - false, - [], - {}, - 2 ** 63 - 1, - -(2 ** 63 + 1), - 2 ** 31 - 1, - -(2 ** 31 + 1), - 2 ** 15, - -(2 ** 15 + 1), - "foo", - "123ABC", - "ABC123", - "12AB3C", - ])("rejects %s", (value) => { - expect(() => strictParseShort(value as any)).toThrowError(); - }); - }); -}); - -describe("strictParseByte", () => { - describe("accepts integers", () => { - describe("accepts 8-bit integers", () => { - it.each([1, 127, -128])("accepts %s", (value) => { - expect(strictParseByte(value)).toEqual(value); - }); - }); - expect(strictParseByte("1")).toEqual(1); - }); - - it.each([null, undefined])("accepts %s", (value) => { - expect(strictParseByte(value)).toEqual(undefined); - }); - - describe("rejects non-integers", () => { - it.each([ - 1.1, - "1.1", - "NaN", - "Infinity", - "-Infinity", - NaN, - Infinity, - -Infinity, - true, - false, - [], - {}, - 2 ** 63 - 1, - -(2 ** 63 + 1), - 2 ** 31 - 1, - -(2 ** 31 + 1), - 2 ** 15, - -(2 ** 15 + 1), - 128, - -129, - "foo", - "123ABC", - "ABC123", - "12AB3C", - ])("rejects %s", (value) => { - expect(() => strictParseByte(value as any)).toThrowError(); - }); - }); -}); diff --git a/packages/smithy-client/src/parse-utils.ts b/packages/smithy-client/src/parse-utils.ts deleted file mode 100644 index 7d02f7a2765b..000000000000 --- a/packages/smithy-client/src/parse-utils.ts +++ /dev/null @@ -1,558 +0,0 @@ -/** - * @internal - * - * Give an input string, strictly parses a boolean value. - * - * @param value - The boolean string to parse. - * @returns true for "true", false for "false", otherwise an error is thrown. - */ -export const parseBoolean = (value: string): boolean => { - switch (value) { - case "true": - return true; - case "false": - return false; - default: - throw new Error(`Unable to parse boolean value "${value}"`); - } -}; - -/** - * @internal - * - * Asserts a value is a boolean and returns it. - * Casts strings and numbers with a warning if there is evidence that they were - * intended to be booleans. - * - * @param value - A value that is expected to be a boolean. - * @returns The value if it's a boolean, undefined if it's null/undefined, - * otherwise an error is thrown. - */ -export const expectBoolean = (value: any): boolean | undefined => { - if (value === null || value === undefined) { - return undefined; - } - if (typeof value === "number") { - if (value === 0 || value === 1) { - logger.warn(stackTraceWarning(`Expected boolean, got ${typeof value}: ${value}`)); - } - if (value === 0) { - return false; - } - if (value === 1) { - return true; - } - } - if (typeof value === "string") { - const lower = value.toLowerCase(); - if (lower === "false" || lower === "true") { - logger.warn(stackTraceWarning(`Expected boolean, got ${typeof value}: ${value}`)); - } - if (lower === "false") { - return false; - } - if (lower === "true") { - return true; - } - } - if (typeof value === "boolean") { - return value; - } - throw new TypeError(`Expected boolean, got ${typeof value}: ${value}`); -}; - -/** - * @internal - * - * Asserts a value is a number and returns it. - * Casts strings with a warning if the string is a parseable number. - * This is to unblock slight API definition/implementation inconsistencies. - * - * @param value - A value that is expected to be a number. - * @returns The value if it's a number, undefined if it's null/undefined, - * otherwise an error is thrown. - */ -export const expectNumber = (value: any): number | undefined => { - if (value === null || value === undefined) { - return undefined; - } - if (typeof value === "string") { - const parsed = parseFloat(value); - if (!Number.isNaN(parsed)) { - if (String(parsed) !== String(value)) { - logger.warn(stackTraceWarning(`Expected number but observed string: ${value}`)); - } - return parsed; - } - } - if (typeof value === "number") { - return value; - } - throw new TypeError(`Expected number, got ${typeof value}: ${value}`); -}; - -const MAX_FLOAT = Math.ceil(2 ** 127 * (2 - 2 ** -23)); - -/** - * @internal - * - * Asserts a value is a 32-bit float and returns it. - * - * @param value - A value that is expected to be a 32-bit float. - * @returns The value if it's a float, undefined if it's null/undefined, - * otherwise an error is thrown. - */ -export const expectFloat32 = (value: any): number | undefined => { - const expected = expectNumber(value); - if (expected !== undefined && !Number.isNaN(expected) && expected !== Infinity && expected !== -Infinity) { - // IEEE-754 is an imperfect representation for floats. Consider the simple - // value `0.1`. The representation in a 32-bit float would look like: - // - // 0 01111011 10011001100110011001101 - // Actual value: 0.100000001490116119384765625 - // - // Note the repeating pattern of `1001` in the fraction part. The 64-bit - // representation is similar: - // - // 0 01111111011 1001100110011001100110011001100110011001100110011010 - // Actual value: 0.100000000000000005551115123126 - // - // So even for what we consider simple numbers, the representation differs - // between the two formats. And it's non-obvious how one might look at the - // 64-bit value (which is how JS represents numbers) and determine if it - // can be represented reasonably in the 32-bit form. Primarily because you - // can't know whether the intent was to represent `0.1` or the actual - // value in memory. But even if you have both the decimal value and the - // double value, that still doesn't communicate the intended precision. - // - // So rather than attempting to divine the intent of the caller, we instead - // do some simple bounds checking to make sure the value is passingly - // representable in a 32-bit float. It's not perfect, but it's good enough. - // Perfect, even if possible to achieve, would likely be too costly to - // be worth it. - // - // The maximum value of a 32-bit float. Since the 64-bit representation - // could be more or less, we just round it up to the nearest whole number. - // This further reduces our ability to be certain of the value, but it's - // an acceptable tradeoff. - // - // Compare against the absolute value to simplify things. - if (Math.abs(expected) > MAX_FLOAT) { - throw new TypeError(`Expected 32-bit float, got ${value}`); - } - } - return expected; -}; - -/** - * @internal - * - * Asserts a value is an integer and returns it. - * - * @param value - A value that is expected to be an integer. - * @returns The value if it's an integer, undefined if it's null/undefined, - * otherwise an error is thrown. - */ -export const expectLong = (value: any): number | undefined => { - if (value === null || value === undefined) { - return undefined; - } - if (Number.isInteger(value) && !Number.isNaN(value)) { - return value; - } - throw new TypeError(`Expected integer, got ${typeof value}: ${value}`); -}; - -/** - * @internal - * - * @deprecated Use expectLong - */ -export const expectInt = expectLong; - -/** - * @internal - * - * Asserts a value is a 32-bit integer and returns it. - * - * @param value - A value that is expected to be an integer. - * @returns The value if it's an integer, undefined if it's null/undefined, - * otherwise an error is thrown. - */ -export const expectInt32 = (value: any): number | undefined => expectSizedInt(value, 32); - -/** - * @internal - * - * Asserts a value is a 16-bit integer and returns it. - * - * @param value - A value that is expected to be an integer. - * @returns The value if it's an integer, undefined if it's null/undefined, - * otherwise an error is thrown. - */ -export const expectShort = (value: any): number | undefined => expectSizedInt(value, 16); - -/** - * @internal - * - * Asserts a value is an 8-bit integer and returns it. - * - * @param value - A value that is expected to be an integer. - * @returns The value if it's an integer, undefined if it's null/undefined, - * otherwise an error is thrown. - */ -export const expectByte = (value: any): number | undefined => expectSizedInt(value, 8); - -type IntSize = 32 | 16 | 8; - -const expectSizedInt = (value: any, size: IntSize): number | undefined => { - const expected = expectLong(value); - if (expected !== undefined && castInt(expected, size) !== expected) { - throw new TypeError(`Expected ${size}-bit integer, got ${value}`); - } - return expected; -}; - -const castInt = (value: number, size: IntSize) => { - switch (size) { - case 32: - return Int32Array.of(value)[0]; - case 16: - return Int16Array.of(value)[0]; - case 8: - return Int8Array.of(value)[0]; - } -}; - -/** - * @internal - * - * Asserts a value is not null or undefined and returns it, or throws an error. - * - * @param value - A value that is expected to be defined - * @param location - The location where we're expecting to find a defined object (optional) - * @returns The value if it's not undefined, otherwise throws an error - */ -export const expectNonNull = (value: T | null | undefined, location?: string): T => { - if (value === null || value === undefined) { - if (location) { - throw new TypeError(`Expected a non-null value for ${location}`); - } - throw new TypeError("Expected a non-null value"); - } - return value; -}; - -/** - * @internal - * - * Asserts a value is an JSON-like object and returns it. This is expected to be used - * with values parsed from JSON (arrays, objects, numbers, strings, booleans). - * - * @param value - A value that is expected to be an object - * @returns The value if it's an object, undefined if it's null/undefined, - * otherwise an error is thrown. - */ -export const expectObject = (value: any): Record | undefined => { - if (value === null || value === undefined) { - return undefined; - } - if (typeof value === "object" && !Array.isArray(value)) { - return value; - } - const receivedType = Array.isArray(value) ? "array" : typeof value; - throw new TypeError(`Expected object, got ${receivedType}: ${value}`); -}; - -/** - * @internal - * - * Asserts a value is a string and returns it. - * Numbers and boolean will be cast to strings with a warning. - * - * @param value - A value that is expected to be a string. - * @returns The value if it's a string, undefined if it's null/undefined, - * otherwise an error is thrown. - */ -export const expectString = (value: any): string | undefined => { - if (value === null || value === undefined) { - return undefined; - } - if (typeof value === "string") { - return value; - } - if (["boolean", "number", "bigint"].includes(typeof value)) { - logger.warn(stackTraceWarning(`Expected string, got ${typeof value}: ${value}`)); - return String(value); - } - throw new TypeError(`Expected string, got ${typeof value}: ${value}`); -}; - -/** - * @internal - * - * Asserts a value is a JSON-like object with only one non-null/non-undefined key and - * returns it. - * - * @param value - A value that is expected to be an object with exactly one non-null, - * non-undefined key. - * @returns the value if it's a union, undefined if it's null/undefined, otherwise - * an error is thrown. - */ -export const expectUnion = (value: unknown): Record | undefined => { - if (value === null || value === undefined) { - return undefined; - } - const asObject = expectObject(value)!; - - const setKeys = Object.entries(asObject) - .filter(([, v]) => v != null) - .map(([k]) => k); - - if (setKeys.length === 0) { - throw new TypeError(`Unions must have exactly one non-null member. None were found.`); - } - - if (setKeys.length > 1) { - throw new TypeError(`Unions must have exactly one non-null member. Keys ${setKeys} were not null.`); - } - - return asObject; -}; - -/** - * @internal - * - * Parses a value into a double. If the value is null or undefined, undefined - * will be returned. If the value is a string, it will be parsed by the standard - * parseFloat with one exception: NaN may only be explicitly set as the string - * "NaN", any implicit Nan values will result in an error being thrown. If any - * other type is provided, an exception will be thrown. - * - * @param value - A number or string representation of a double. - * @returns The value as a number, or undefined if it's null/undefined. - */ -export const strictParseDouble = (value: string | number): number | undefined => { - if (typeof value == "string") { - return expectNumber(parseNumber(value)); - } - return expectNumber(value); -}; - -/** - * @internal - * - * @deprecated Use strictParseDouble - */ -export const strictParseFloat = strictParseDouble; - -/** - * @internal - * - * Parses a value into a float. If the value is null or undefined, undefined - * will be returned. If the value is a string, it will be parsed by the standard - * parseFloat with one exception: NaN may only be explicitly set as the string - * "NaN", any implicit Nan values will result in an error being thrown. If any - * other type is provided, an exception will be thrown. - * - * @param value - A number or string representation of a float. - * @returns The value as a number, or undefined if it's null/undefined. - */ -export const strictParseFloat32 = (value: string | number): number | undefined => { - if (typeof value == "string") { - return expectFloat32(parseNumber(value)); - } - return expectFloat32(value); -}; - -// This regex matches JSON-style numbers. In short: -// * The integral may start with a negative sign, but not a positive one -// * No leading 0 on the integral unless it's immediately followed by a '.' -// * Exponent indicated by a case-insensitive 'E' optionally followed by a -// positive/negative sign and some number of digits. -// It also matches both positive and negative infinity as well and explicit NaN. -const NUMBER_REGEX = /(-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)|(-?Infinity)|(NaN)/g; - -const parseNumber = (value: string): number => { - const matches = value.match(NUMBER_REGEX); - if (matches === null || matches[0].length !== value.length) { - throw new TypeError(`Expected real number, got implicit NaN`); - } - return parseFloat(value); -}; - -/** - * @internal - * - * Asserts a value is a number and returns it. If the value is a string - * representation of a non-numeric number type (NaN, Infinity, -Infinity), - * the value will be parsed. Any other string value will result in an exception - * being thrown. Null or undefined will be returned as undefined. Any other - * type will result in an exception being thrown. - * - * @param value - A number or string representation of a non-numeric float. - * @returns The value as a number, or undefined if it's null/undefined. - */ -export const limitedParseDouble = (value: string | number): number | undefined => { - if (typeof value == "string") { - return parseFloatString(value); - } - return expectNumber(value); -}; - -/** - * @internal - * - * @deprecated Use limitedParseDouble - */ -export const handleFloat = limitedParseDouble; - -/** - * @internal - * - * @deprecated Use limitedParseDouble - */ -export const limitedParseFloat = limitedParseDouble; - -/** - * @internal - * - * Asserts a value is a 32-bit float and returns it. If the value is a string - * representation of a non-numeric number type (NaN, Infinity, -Infinity), - * the value will be parsed. Any other string value will result in an exception - * being thrown. Null or undefined will be returned as undefined. Any other - * type will result in an exception being thrown. - * - * @param value - A number or string representation of a non-numeric float. - * @returns The value as a number, or undefined if it's null/undefined. - */ -export const limitedParseFloat32 = (value: string | number): number | undefined => { - if (typeof value == "string") { - return parseFloatString(value); - } - return expectFloat32(value); -}; - -const parseFloatString = (value: string): number => { - switch (value) { - case "NaN": - return NaN; - case "Infinity": - return Infinity; - case "-Infinity": - return -Infinity; - default: - throw new Error(`Unable to parse float value: ${value}`); - } -}; - -/** - * @internal - * - * Parses a value into an integer. If the value is null or undefined, undefined - * will be returned. If the value is a string, it will be parsed by parseFloat - * and the result will be asserted to be an integer. If the parsed value is not - * an integer, or the raw value is any type other than a string or number, an - * exception will be thrown. - * - * @param value - A number or string representation of an integer. - * @returns The value as a number, or undefined if it's null/undefined. - */ -export const strictParseLong = (value: string | number): number | undefined => { - if (typeof value === "string") { - // parseInt can't be used here, because it will silently discard any - // existing decimals. We want to instead throw an error if there are any. - return expectLong(parseNumber(value)); - } - return expectLong(value); -}; - -/** - * @internal - * - * @deprecated Use strictParseLong - */ -export const strictParseInt = strictParseLong; - -/** - * @internal - * - * Parses a value into a 32-bit integer. If the value is null or undefined, undefined - * will be returned. If the value is a string, it will be parsed by parseFloat - * and the result will be asserted to be an integer. If the parsed value is not - * an integer, or the raw value is any type other than a string or number, an - * exception will be thrown. - * - * @param value - A number or string representation of a 32-bit integer. - * @returns The value as a number, or undefined if it's null/undefined. - */ -export const strictParseInt32 = (value: string | number): number | undefined => { - if (typeof value === "string") { - // parseInt can't be used here, because it will silently discard any - // existing decimals. We want to instead throw an error if there are any. - return expectInt32(parseNumber(value)); - } - return expectInt32(value); -}; - -/** - * @internal - * - * Parses a value into a 16-bit integer. If the value is null or undefined, undefined - * will be returned. If the value is a string, it will be parsed by parseFloat - * and the result will be asserted to be an integer. If the parsed value is not - * an integer, or the raw value is any type other than a string or number, an - * exception will be thrown. - * - * @param value - A number or string representation of a 16-bit integer. - * @returns The value as a number, or undefined if it's null/undefined. - */ -export const strictParseShort = (value: string | number): number | undefined => { - if (typeof value === "string") { - // parseInt can't be used here, because it will silently discard any - // existing decimals. We want to instead throw an error if there are any. - return expectShort(parseNumber(value)); - } - return expectShort(value); -}; - -/** - * @internal - * - * Parses a value into an 8-bit integer. If the value is null or undefined, undefined - * will be returned. If the value is a string, it will be parsed by parseFloat - * and the result will be asserted to be an integer. If the parsed value is not - * an integer, or the raw value is any type other than a string or number, an - * exception will be thrown. - * - * @param value - A number or string representation of an 8-bit integer. - * @returns The value as a number, or undefined if it's null/undefined. - */ -export const strictParseByte = (value: string | number): number | undefined => { - if (typeof value === "string") { - // parseInt can't be used here, because it will silently discard any - // existing decimals. We want to instead throw an error if there are any. - return expectByte(parseNumber(value)); - } - return expectByte(value); -}; - -/** - * @internal - * @param message - error message. - * @returns truncated stack trace omitting this function. - */ -const stackTraceWarning = (message: string): string => { - return String(new TypeError(message).stack || message) - .split("\n") - .slice(0, 5) - .filter((s) => !s.includes("stackTraceWarning")) - .join("\n"); -}; - -/** - * @internal - */ -export const logger = { - warn: console.warn, -}; diff --git a/packages/smithy-client/src/resolve-path.ts b/packages/smithy-client/src/resolve-path.ts deleted file mode 100644 index 53a86bb198d4..000000000000 --- a/packages/smithy-client/src/resolve-path.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { extendedEncodeURIComponent } from "./extended-encode-uri-component"; - -/** - * @internal - */ -export const resolvedPath = ( - resolvedPath: string, - input: unknown, - memberName: string, - labelValueProvider: () => string | undefined, - uriLabel: string, - isGreedyLabel: boolean -): string => { - if (input != null && (input as Record)[memberName] !== undefined) { - const labelValue = labelValueProvider() as string; - if (labelValue.length <= 0) { - throw new Error("Empty value provided for input HTTP label: " + memberName + "."); - } - resolvedPath = resolvedPath.replace( - uriLabel, - isGreedyLabel - ? labelValue - .split("/") - .map((segment) => extendedEncodeURIComponent(segment)) - .join("/") - : extendedEncodeURIComponent(labelValue) - ); - } else { - throw new Error("No value provided for input HTTP label: " + memberName + "."); - } - return resolvedPath; -}; diff --git a/packages/smithy-client/src/ser-utils.spec.ts b/packages/smithy-client/src/ser-utils.spec.ts deleted file mode 100644 index 68a12efe8632..000000000000 --- a/packages/smithy-client/src/ser-utils.spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { serializeFloat } from "./ser-utils"; - -describe("serializeFloat", () => { - it("handles non-numerics", () => { - expect(serializeFloat(NaN)).toEqual("NaN"); - expect(serializeFloat(Infinity)).toEqual("Infinity"); - expect(serializeFloat(-Infinity)).toEqual("-Infinity"); - }); - - it("handles normal numbers", () => { - expect(serializeFloat(1)).toEqual(1); - expect(serializeFloat(1.1)).toEqual(1.1); - }); -}); diff --git a/packages/smithy-client/src/ser-utils.ts b/packages/smithy-client/src/ser-utils.ts deleted file mode 100644 index 7b340a622bf3..000000000000 --- a/packages/smithy-client/src/ser-utils.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @internal - * - * Serializes a number, turning non-numeric values into strings. - * - * @param value - The number to serialize. - * @returns A number, or a string if the given number was non-numeric. - */ -export const serializeFloat = (value: number): string | number => { - // NaN is not equal to everything, including itself. - if (value !== value) { - return "NaN"; - } - switch (value) { - case Infinity: - return "Infinity"; - case -Infinity: - return "-Infinity"; - default: - return value; - } -}; diff --git a/packages/smithy-client/src/serde-json.spec.ts b/packages/smithy-client/src/serde-json.spec.ts deleted file mode 100644 index cca3b35eabd6..000000000000 --- a/packages/smithy-client/src/serde-json.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { _json } from "./serde-json"; - -describe(_json.name, () => { - it("removes nullish entries", () => { - expect( - _json({ - g: void 0, - a: { - e: void 0, - b: { - c: { - f: void 0, - }, - d: void 0, - }, - }, - }) - ).toEqual({ - a: { - b: { - c: {}, - }, - }, - }); - }); - - it("filters sparse lists", () => { - expect( - _json({ - a: { - b: 5, - c: [, , , 6], - }, - }) - ).toEqual({ - a: { b: 5, c: [6] }, - }); - }); -}); diff --git a/packages/smithy-client/src/serde-json.ts b/packages/smithy-client/src/serde-json.ts deleted file mode 100644 index 064c1a9404da..000000000000 --- a/packages/smithy-client/src/serde-json.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @internal - * - * Maps an object through the default JSON serde behavior. - * This means removing nullish fields and un-sparsifying lists. - * - * @param obj - to be checked. - * @returns same object with default serde behavior applied. - */ -export const _json = (obj: any): any => { - if (obj == null) { - return {}; - } - if (Array.isArray(obj)) { - return obj.filter((_: any) => _ != null); - } - if (typeof obj === "object") { - const target: any = {}; - for (const key of Object.keys(obj)) { - if (obj[key] == null) { - continue; - } - target[key] = _json(obj[key]); - } - return target; - } - return obj; -}; diff --git a/packages/smithy-client/src/split-every.spec.ts b/packages/smithy-client/src/split-every.spec.ts deleted file mode 100644 index e0beecd39c03..000000000000 --- a/packages/smithy-client/src/split-every.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { splitEvery } from "./split-every"; -describe("splitEvery", () => { - const m1 = "foo"; - const m2 = "foo, bar"; - const m3 = "foo, bar, baz"; - const m4 = "foo, bar, baz, qux"; - const m5 = "foo, bar, baz, qux, coo"; - const m6 = "foo, bar, baz, qux, coo, tan"; - const delim = ", "; - - it("Errors on <= 0", () => { - expect(() => { - splitEvery(m2, delim, -1); - }).toThrow("Invalid number of delimiters"); - - expect(() => { - splitEvery(m2, delim, 0); - }).toThrow("Invalid number of delimiters"); - }); - - it("Errors on non-integer", () => { - expect(() => { - splitEvery(m2, delim, 1.3); - }).toThrow("Invalid number of delimiters"); - - expect(() => { - splitEvery(m2, delim, 4.9); - }).toThrow("Invalid number of delimiters"); - }); - - it("Handles splitting on 1", () => { - const count = 1; - expect(splitEvery(m1, delim, count)).toMatchObject(m1.split(delim)); - expect(splitEvery(m2, delim, count)).toMatchObject(m2.split(delim)); - expect(splitEvery(m3, delim, count)).toMatchObject(m3.split(delim)); - expect(splitEvery(m4, delim, count)).toMatchObject(m4.split(delim)); - expect(splitEvery(m5, delim, count)).toMatchObject(m5.split(delim)); - expect(splitEvery(m6, delim, count)).toMatchObject(m6.split(delim)); - }); - - it("Handles splitting on 2", () => { - const count = 2; - expect(splitEvery(m1, delim, count)).toMatchObject(["foo"]); - expect(splitEvery(m2, delim, count)).toMatchObject(["foo, bar"]); - expect(splitEvery(m3, delim, count)).toMatchObject(["foo, bar", "baz"]); - expect(splitEvery(m4, delim, count)).toMatchObject(["foo, bar", "baz, qux"]); - expect(splitEvery(m5, delim, count)).toMatchObject(["foo, bar", "baz, qux", "coo"]); - expect(splitEvery(m6, delim, count)).toMatchObject(["foo, bar", "baz, qux", "coo, tan"]); - }); - - it("Handles splitting on 3", () => { - const count = 3; - expect(splitEvery(m1, delim, count)).toMatchObject(["foo"]); - expect(splitEvery(m2, delim, count)).toMatchObject(["foo, bar"]); - expect(splitEvery(m3, delim, count)).toMatchObject(["foo, bar, baz"]); - expect(splitEvery(m4, delim, count)).toMatchObject(["foo, bar, baz", "qux"]); - expect(splitEvery(m5, delim, count)).toMatchObject(["foo, bar, baz", "qux, coo"]); - expect(splitEvery(m6, delim, count)).toMatchObject(["foo, bar, baz", "qux, coo, tan"]); - }); -}); diff --git a/packages/smithy-client/src/split-every.ts b/packages/smithy-client/src/split-every.ts deleted file mode 100644 index 2644fb7bee40..000000000000 --- a/packages/smithy-client/src/split-every.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @internal - * - * Given an input string, splits based on the delimiter after a given - * number of delimiters has been encountered. - * - * @param value - The input string to split. - * @param delimiter - The delimiter to split on. - * @param numDelimiters - The number of delimiters to have encountered to split. - */ -export function splitEvery(value: string, delimiter: string, numDelimiters: number): Array { - // Fail if we don't have a clear number to split on. - if (numDelimiters <= 0 || !Number.isInteger(numDelimiters)) { - throw new Error("Invalid number of delimiters (" + numDelimiters + ") for splitEvery."); - } - - const segments = value.split(delimiter); - // Short circuit extra logic for the simple case. - if (numDelimiters === 1) { - return segments; - } - - const compoundSegments: Array = []; - let currentSegment = ""; - for (let i = 0; i < segments.length; i++) { - if (currentSegment === "") { - // Start a new segment. - currentSegment = segments[i]; - } else { - // Compound the current segment with the delimiter. - currentSegment += delimiter + segments[i]; - } - - if ((i + 1) % numDelimiters === 0) { - // We encountered the right number of delimiters, so add the entry. - compoundSegments.push(currentSegment); - // And reset the current segment. - currentSegment = ""; - } - } - - // Handle any leftover segment portion. - if (currentSegment !== "") { - compoundSegments.push(currentSegment); - } - - return compoundSegments; -} diff --git a/packages/url-parser/jest.config.js b/packages/url-parser/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/url-parser/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/url-parser/package.json b/packages/url-parser/package.json index c54d82b6d6d6..41a6f27d47ad 100644 --- a/packages/url-parser/package.json +++ b/packages/url-parser/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,8 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/querystring-parser": "*", - "@aws-sdk/types": "*", + "@smithy/url-parser": "^1.0.1", "tslib": "^2.5.0" }, "typesVersions": { diff --git a/packages/url-parser/src/index.spec.ts b/packages/url-parser/src/index.spec.ts deleted file mode 100644 index 303ebef60404..000000000000 --- a/packages/url-parser/src/index.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Endpoint } from "@aws-sdk/types"; - -import { parseUrl } from "."; - -describe("parseUrl", () => { - const testCases = new Map([ - [ - "https://www.example.com/path/to%20the/file.ext?snap=cr%C3%A4ckle&snap=p%C3%B4p&fizz=buzz&quux", - { - protocol: "https:", - hostname: "www.example.com", - path: "/path/to%20the/file.ext", - query: { - snap: ["cräckle", "pôp"], - fizz: "buzz", - quux: null, - }, - }, - ], - [ - "http://example.com:54321", - { - protocol: "http:", - hostname: "example.com", - port: 54321, - path: "/", - }, - ], - [ - "https://example.com?foo=bar", - { - protocol: "https:", - hostname: "example.com", - path: "/", - query: { foo: "bar" }, - }, - ], - ]); - - const testFunc = typeof URL !== "undefined" ? it : xit; - - for (const [url, parsed] of testCases) { - testFunc(`should correctly parse ${url}`, () => { - expect(parseUrl(url)).toEqual(parsed); - }); - } -}); diff --git a/packages/url-parser/src/index.ts b/packages/url-parser/src/index.ts index b29f92967928..8e2c06295c6b 100644 --- a/packages/url-parser/src/index.ts +++ b/packages/url-parser/src/index.ts @@ -1,25 +1 @@ -import { parseQueryString } from "@aws-sdk/querystring-parser"; -import { Endpoint, QueryParameterBag, UrlParser } from "@aws-sdk/types"; - -/** - * @internal - */ -export const parseUrl: UrlParser = (url: string | URL): Endpoint => { - if (typeof url === "string") { - return parseUrl(new URL(url)); - } - const { hostname, pathname, port, protocol, search } = url as URL; - - let query: QueryParameterBag | undefined; - if (search) { - query = parseQueryString(search); - } - - return { - hostname, - port: port ? parseInt(port) : undefined, - protocol, - path: pathname, - query, - }; -}; +export * from "@smithy/url-parser"; diff --git a/packages/util-base64/jest.config.js b/packages/util-base64/jest.config.js deleted file mode 100644 index 95d8863b22a1..000000000000 --- a/packages/util-base64/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, - testMatch: ["**/*.spec.ts"], -}; diff --git a/packages/util-base64/package.json b/packages/util-base64/package.json index 994683e6950c..7be34c5bd97c 100644 --- a/packages/util-base64/package.json +++ b/packages/util-base64/package.json @@ -12,7 +12,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", @@ -20,7 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/util-buffer-from": "*", + "@smithy/util-base64": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/util-base64/src/__mocks__/testCases.json b/packages/util-base64/src/__mocks__/testCases.json deleted file mode 100644 index 1c673ecdd5a9..000000000000 --- a/packages/util-base64/src/__mocks__/testCases.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - ["double padded", "3q2+7w==", [222, 173, 190, 239]], - ["single padded", "3q2+7/o=", [222, 173, 190, 239, 250]], - ["unpadded", "3q2+7/rO", [222, 173, 190, 239, 250, 206]], - ["AAAA", "AAAA", [0, 0, 0]], - ["AAAB", "AAAB", [0, 0, 1]], - ["AAH/", "AAH/", [0, 1, -1]], - ["AQEB", "AQEB", [1, 1, 1]], - ["ALcX", "ALcX", [0, -73, 23]] -] diff --git a/packages/util-base64/src/constants.browser.ts b/packages/util-base64/src/constants.browser.ts deleted file mode 100644 index 256d216ef7b8..000000000000 --- a/packages/util-base64/src/constants.browser.ts +++ /dev/null @@ -1,34 +0,0 @@ -const alphabetByEncoding: Record = {}; -const alphabetByValue: Array = new Array(64); - -for (let i = 0, start = "A".charCodeAt(0), limit = "Z".charCodeAt(0); i + start <= limit; i++) { - const char = String.fromCharCode(i + start); - alphabetByEncoding[char] = i; - alphabetByValue[i] = char; -} - -for (let i = 0, start = "a".charCodeAt(0), limit = "z".charCodeAt(0); i + start <= limit; i++) { - const char = String.fromCharCode(i + start); - const index = i + 26; - alphabetByEncoding[char] = index; - alphabetByValue[index] = char; -} - -for (let i = 0; i < 10; i++) { - alphabetByEncoding[i.toString(10)] = i + 52; - const char = i.toString(10); - const index = i + 52; - alphabetByEncoding[char] = index; - alphabetByValue[index] = char; -} - -alphabetByEncoding["+"] = 62; -alphabetByValue[62] = "+"; -alphabetByEncoding["/"] = 63; -alphabetByValue[63] = "/"; - -const bitsPerLetter = 6; -const bitsPerByte = 8; -const maxLetterValue = 0b111111; - -export { alphabetByEncoding, alphabetByValue, bitsPerLetter, bitsPerByte, maxLetterValue }; diff --git a/packages/util-base64/src/fromBase64.browser.spec.ts b/packages/util-base64/src/fromBase64.browser.spec.ts deleted file mode 100644 index 9209f99d9b07..000000000000 --- a/packages/util-base64/src/fromBase64.browser.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @jest-environment jsdom - */ -import testCases from "./__mocks__/testCases.json"; -import { fromBase64 } from "./fromBase64.browser"; - -describe(fromBase64.name, () => { - it.each(testCases as Array<[string, string, number[]]>)("%s", (desc, encoded, decoded) => { - expect(fromBase64(encoded)).toEqual(new Uint8Array(decoded)); - }); - - it("should throw when given a number", () => { - expect(() => fromBase64(0xdeadbeefface as any)).toThrow(); - }); - - describe("should reject invalid base64 strings", () => { - it.each(["Rg", "Rg=", "[][]", "-_=="])("rejects '%s'", (value) => { - expect(() => fromBase64(value)).toThrowError(); - }); - }); -}); diff --git a/packages/util-base64/src/fromBase64.browser.ts b/packages/util-base64/src/fromBase64.browser.ts deleted file mode 100644 index 5bde6f89dcb0..000000000000 --- a/packages/util-base64/src/fromBase64.browser.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { alphabetByEncoding, bitsPerByte, bitsPerLetter } from "./constants.browser"; - -/** - * Converts a base-64 encoded string to a Uint8Array of bytes. - * - * @param input The base-64 encoded string - * - * @see https://tools.ietf.org/html/rfc4648#section-4 - */ -export const fromBase64 = (input: string): Uint8Array => { - let totalByteLength = (input.length / 4) * 3; - if (input.slice(-2) === "==") { - totalByteLength -= 2; - } else if (input.slice(-1) === "=") { - totalByteLength--; - } - const out = new ArrayBuffer(totalByteLength); - const dataView = new DataView(out); - for (let i = 0; i < input.length; i += 4) { - let bits = 0; - let bitLength = 0; - for (let j = i, limit = i + 3; j <= limit; j++) { - if (input[j] !== "=") { - // If we don't check for this, we'll end up using undefined in a bitwise - // operation, in which it will be treated as 0. - if (!(input[j] in alphabetByEncoding)) { - throw new TypeError(`Invalid character ${input[j]} in base64 string.`); - } - bits |= alphabetByEncoding[input[j]] << ((limit - j) * bitsPerLetter); - bitLength += bitsPerLetter; - } else { - bits >>= bitsPerLetter; - } - } - - const chunkOffset = (i / 4) * 3; - bits >>= bitLength % bitsPerByte; - const byteLength = Math.floor(bitLength / bitsPerByte); - for (let k = 0; k < byteLength; k++) { - const offset = (byteLength - k - 1) * bitsPerByte; - dataView.setUint8(chunkOffset + k, (bits & (255 << offset)) >> offset); - } - } - - return new Uint8Array(out); -}; diff --git a/packages/util-base64/src/fromBase64.spec.ts b/packages/util-base64/src/fromBase64.spec.ts deleted file mode 100644 index af3c200efaef..000000000000 --- a/packages/util-base64/src/fromBase64.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import testCases from "./__mocks__/testCases.json"; -import { fromBase64 } from "./fromBase64"; - -describe(fromBase64.name, () => { - it.each(testCases as Array<[string, string, number[]]>)("%s", (desc, encoded, decoded) => { - expect(fromBase64(encoded)).toEqual(new Uint8Array(decoded)); - }); - - it("should throw when given a number", () => { - expect(() => fromBase64(0xdeadbeefface as any)).toThrow(); - }); - - describe("should reject invalid base64 strings", () => { - it.each(["Rg", "Rg=", "[][]", "-_=="])("rejects '%s'", (value) => { - expect(() => fromBase64(value)).toThrowError(); - }); - }); -}); diff --git a/packages/util-base64/src/fromBase64.ts b/packages/util-base64/src/fromBase64.ts deleted file mode 100644 index dda64424e329..000000000000 --- a/packages/util-base64/src/fromBase64.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { fromString } from "@aws-sdk/util-buffer-from"; - -const BASE64_REGEX = /^[A-Za-z0-9+/]*={0,2}$/; - -/** - * Converts a base-64 encoded string to a Uint8Array of bytes using Node.JS's - * `buffer` module. - * - * @param input The base-64 encoded string - */ -export const fromBase64 = (input: string): Uint8Array => { - // Node's buffer module allows padding to be omitted, but we want to enforce - // it. So here we ensure that the input represents a number of bits divisible - // by 8. Each character represents 6 bits, so after reducing the fraction we - // end up mulitplying by 3/4 and checking for a remainder. - if ((input.length * 3) % 4 !== 0) { - throw new TypeError(`Incorrect padding on base64 string.`); - } - - // Node will just ignore invalid characters, so we need to make sure they're - // properly rejected. - if (!BASE64_REGEX.exec(input)) { - throw new TypeError(`Invalid base64 string.`); - } - - const buffer = fromString(input, "base64"); - - return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); -}; diff --git a/packages/util-base64/src/index.ts b/packages/util-base64/src/index.ts index 594bd43506cf..0bc94db6b264 100644 --- a/packages/util-base64/src/index.ts +++ b/packages/util-base64/src/index.ts @@ -1,2 +1 @@ -export * from "./fromBase64"; -export * from "./toBase64"; +export * from "@smithy/util-base64"; diff --git a/packages/util-base64/src/toBase64.browser.spec.ts b/packages/util-base64/src/toBase64.browser.spec.ts deleted file mode 100644 index 914e47c46150..000000000000 --- a/packages/util-base64/src/toBase64.browser.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @jest-environment jsdom - */ -import testCases from "./__mocks__/testCases.json"; -import { toBase64 } from "./toBase64.browser"; - -describe(toBase64.name, () => { - it.each(testCases as Array<[string, string, number[]]>)("%s", (desc, encoded, decoded) => { - expect(toBase64(new Uint8Array(decoded))).toEqual(encoded); - }); -}); diff --git a/packages/util-base64/src/toBase64.browser.ts b/packages/util-base64/src/toBase64.browser.ts deleted file mode 100644 index 41a2695b8254..000000000000 --- a/packages/util-base64/src/toBase64.browser.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { alphabetByValue, bitsPerByte, bitsPerLetter, maxLetterValue } from "./constants.browser"; -/** - * Converts a Uint8Array of binary data to a base-64 encoded string. - * - * @param input The binary data to encode - * - * @see https://tools.ietf.org/html/rfc4648#section-4 - */ -export function toBase64(input: Uint8Array): string { - let str = ""; - for (let i = 0; i < input.length; i += 3) { - let bits = 0; - let bitLength = 0; - for (let j = i, limit = Math.min(i + 3, input.length); j < limit; j++) { - bits |= input[j] << ((limit - j - 1) * bitsPerByte); - bitLength += bitsPerByte; - } - - const bitClusterCount = Math.ceil(bitLength / bitsPerLetter); - bits <<= bitClusterCount * bitsPerLetter - bitLength; - for (let k = 1; k <= bitClusterCount; k++) { - const offset = (bitClusterCount - k) * bitsPerLetter; - str += alphabetByValue[(bits & (maxLetterValue << offset)) >> offset]; - } - - str += "==".slice(0, 4 - bitClusterCount); - } - - return str; -} diff --git a/packages/util-base64/src/toBase64.spec.ts b/packages/util-base64/src/toBase64.spec.ts deleted file mode 100644 index 77b144f1b183..000000000000 --- a/packages/util-base64/src/toBase64.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import testCases from "./__mocks__/testCases.json"; -import { toBase64 } from "./toBase64"; - -describe(toBase64.name, () => { - it.each(testCases as Array<[string, string, number[]]>)("%s", (desc, encoded, decoded) => { - expect(toBase64(new Uint8Array(decoded))).toEqual(encoded); - }); - - it("should throw when given a number", () => { - expect(() => toBase64(0xdeadbeefface as any)).toThrow(); - }); -}); diff --git a/packages/util-base64/src/toBase64.ts b/packages/util-base64/src/toBase64.ts deleted file mode 100644 index da6fefeb13e0..000000000000 --- a/packages/util-base64/src/toBase64.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { fromArrayBuffer } from "@aws-sdk/util-buffer-from"; - -/** - * Converts a Uint8Array of binary data to a base-64 encoded string using - * Node.JS's `buffer` module. - * - * @param input The binary data to encode - */ -export const toBase64 = (input: Uint8Array): string => - fromArrayBuffer(input.buffer, input.byteOffset, input.byteLength).toString("base64"); diff --git a/packages/util-body-length-browser/jest.config.js b/packages/util-body-length-browser/jest.config.js deleted file mode 100644 index bd895a5df03e..000000000000 --- a/packages/util-body-length-browser/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, - testEnvironment: "jsdom", -}; diff --git a/packages/util-body-length-browser/package.json b/packages/util-body-length-browser/package.json index 57e6bfb16e8a..d4b2de302bd9 100644 --- a/packages/util-body-length-browser/package.json +++ b/packages/util-body-length-browser/package.json @@ -10,7 +10,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -21,6 +21,7 @@ }, "license": "Apache-2.0", "dependencies": { + "@smithy/util-body-length-browser": "^1.0.1", "tslib": "^2.5.0" }, "typesVersions": { diff --git a/packages/util-body-length-browser/src/calculateBodyLength.spec.ts b/packages/util-body-length-browser/src/calculateBodyLength.spec.ts deleted file mode 100644 index 7363894eacec..000000000000 --- a/packages/util-body-length-browser/src/calculateBodyLength.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { calculateBodyLength } from "./calculateBodyLength"; - -describe(calculateBodyLength.name, () => { - describe("should handle string input", () => { - it.each([ - { desc: "basic", input: "foo", output: 3 }, - { desc: "emoji", input: "foo 🥺", output: 8 }, - { desc: "multi-byte characters", input: "2。", output: 4 }, - ])("%s", ({ input, output }) => { - expect(calculateBodyLength(input)).toEqual(output); - }); - }); - - describe("should handle input with byteLength", () => { - const sizes = [1, 256, 65536]; - - describe("ArrayBuffer", () => { - it.each(sizes)("size: %s", (size) => { - expect(calculateBodyLength(new ArrayBuffer(size))).toEqual(size); - }); - }); - - describe("TypedArray", () => { - it.each(sizes)("size: %s", (size) => { - expect(calculateBodyLength(new Uint8Array(size))).toEqual(size); - }); - }); - }); - - it("should handle File object", () => { - // Mock File Object https://developer.mozilla.org/en-US/docs/Web/API/File/File#example - const lastModifiedDate = new Date(); - const mockFileObject = { - lastModified: lastModifiedDate.getTime(), - lastModifiedDate, - name: "foo.txt", - size: 3, - type: "text/plain", - webkitRelativePath: "", - }; - expect(calculateBodyLength(mockFileObject)).toEqual(mockFileObject.size); - }); - - describe("throws error if Body Length computation fails", () => { - it.each([true, 1, {}, []])("%s", (body) => { - expect(() => { - expect(calculateBodyLength(body)); - }).toThrowError(`Body Length computation failed for ${body}`); - }); - }); -}); diff --git a/packages/util-body-length-browser/src/calculateBodyLength.ts b/packages/util-body-length-browser/src/calculateBodyLength.ts deleted file mode 100644 index 7a65201c7e5e..000000000000 --- a/packages/util-body-length-browser/src/calculateBodyLength.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @internal - */ -export const calculateBodyLength = (body: any): number | undefined => { - if (typeof body === "string") { - let len = body.length; - - for (let i = len - 1; i >= 0; i--) { - const code = body.charCodeAt(i); - if (code > 0x7f && code <= 0x7ff) len++; - else if (code > 0x7ff && code <= 0xffff) len += 2; - if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate - } - - return len; - } else if (typeof body.byteLength === "number") { - // handles Uint8Array, ArrayBuffer, Buffer, and ArrayBufferView - return body.byteLength; - } else if (typeof body.size === "number") { - // handles browser File object - return body.size; - } - throw new Error(`Body Length computation failed for ${body}`); -}; diff --git a/packages/util-body-length-browser/src/index.ts b/packages/util-body-length-browser/src/index.ts index 7b4a0d7f44d1..558c7e727d8b 100644 --- a/packages/util-body-length-browser/src/index.ts +++ b/packages/util-body-length-browser/src/index.ts @@ -1,4 +1 @@ -/** - * @internal - */ -export * from "./calculateBodyLength"; +export * from "@smithy/util-body-length-browser"; diff --git a/packages/util-body-length-node/jest.config.js b/packages/util-body-length-node/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/util-body-length-node/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/util-body-length-node/package.json b/packages/util-body-length-node/package.json index 6353293446db..bd63e36aa710 100644 --- a/packages/util-body-length-node/package.json +++ b/packages/util-body-length-node/package.json @@ -10,7 +10,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "devDependencies": { "@tsconfig/recommended": "1.0.1", @@ -30,6 +30,7 @@ }, "license": "Apache-2.0", "dependencies": { + "@smithy/util-body-length-node": "^1.0.1", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/util-body-length-node/src/calculateBodyLength.spec.ts b/packages/util-body-length-node/src/calculateBodyLength.spec.ts deleted file mode 100644 index 93416c284c13..000000000000 --- a/packages/util-body-length-node/src/calculateBodyLength.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { createReadStream, lstatSync, promises } from "fs"; - -import { calculateBodyLength } from "./calculateBodyLength"; - -describe(calculateBodyLength.name, () => { - const arrayBuffer = new ArrayBuffer(1); - const typedArray = new Uint8Array(1); - const view = new DataView(arrayBuffer); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it.each([ - [0, null], - [0, undefined], - ])("should return %s for %s", (output, input) => { - expect(calculateBodyLength(input)).toEqual(output); - }); - - it("should handle string inputs", () => { - expect(calculateBodyLength("foo")).toEqual(3); - }); - - it("should handle string inputs with multi-byte characters", () => { - expect(calculateBodyLength("2。")).toEqual(4); - }); - - it("should handle inputs with byteLengths", () => { - expect(calculateBodyLength(arrayBuffer)).toEqual(1); - }); - - it("should handle TypedArray inputs", () => { - expect(calculateBodyLength(typedArray)).toEqual(1); - }); - - it("should handle DataView inputs", () => { - expect(calculateBodyLength(view)).toEqual(1); - }); - - describe("fs.ReadStream", () => { - const fileSize = lstatSync(__filename).size; - - describe("should handle stream created using fs.createReadStream", () => { - it("when path is a string", () => { - const fsReadStream = createReadStream(__filename); - expect(calculateBodyLength(fsReadStream)).toEqual(fileSize); - }); - - it("when path is a Buffer", () => { - const fsReadStream = createReadStream(Buffer.from(__filename)); - expect(calculateBodyLength(fsReadStream)).toEqual(fileSize); - }); - }); - - it("should handle stream created using fd.createReadStream", async () => { - const fd = await promises.open(__filename, "r"); - if ((fd as any).createReadStream) { - const fdReadStream = (fd as any).createReadStream(); - expect(calculateBodyLength(fdReadStream)).toEqual(fileSize); - } - }); - }); - - it.each([true, 1, {}, []])("throws error if Body Length computation fails for: %s", (body) => { - expect(() => { - expect(calculateBodyLength(body)); - }).toThrowError(`Body Length computation failed for ${body}`); - }); -}); diff --git a/packages/util-body-length-node/src/calculateBodyLength.ts b/packages/util-body-length-node/src/calculateBodyLength.ts deleted file mode 100644 index 005ab572a423..000000000000 --- a/packages/util-body-length-node/src/calculateBodyLength.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { fstatSync, lstatSync } from "fs"; - -/** - * @internal - */ -export const calculateBodyLength = (body: any): number | undefined => { - if (!body) { - return 0; - } - if (typeof body === "string") { - return Buffer.from(body).length; - } else if (typeof body.byteLength === "number") { - // handles Uint8Array, ArrayBuffer, Buffer, and ArrayBufferView - return body.byteLength; - } else if (typeof body.size === "number") { - return body.size; - } else if (typeof body.path === "string" || Buffer.isBuffer(body.path)) { - // handles fs readable streams - return lstatSync(body.path).size; - } else if (typeof body.fd === "number") { - // handles fd readable streams - return fstatSync(body.fd).size; - } - throw new Error(`Body Length computation failed for ${body}`); -}; diff --git a/packages/util-body-length-node/src/index.ts b/packages/util-body-length-node/src/index.ts index 7b4a0d7f44d1..d9d7dd1c4d1c 100644 --- a/packages/util-body-length-node/src/index.ts +++ b/packages/util-body-length-node/src/index.ts @@ -1,4 +1 @@ -/** - * @internal - */ -export * from "./calculateBodyLength"; +export * from "@smithy/util-body-length-node"; diff --git a/packages/util-buffer-from/jest.config.js b/packages/util-buffer-from/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/util-buffer-from/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/util-buffer-from/package.json b/packages/util-buffer-from/package.json index fe602d825d90..d287aae7a4d4 100644 --- a/packages/util-buffer-from/package.json +++ b/packages/util-buffer-from/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", @@ -17,7 +17,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/is-array-buffer": "*", + "@smithy/util-buffer-from": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/util-buffer-from/src/index.spec.ts b/packages/util-buffer-from/src/index.spec.ts deleted file mode 100644 index 8e3728a45739..000000000000 --- a/packages/util-buffer-from/src/index.spec.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Buffer } from "buffer"; - -import { fromArrayBuffer, fromString } from "./"; - -jest.mock("buffer"); - -afterEach(() => { - jest.clearAllMocks(); -}); - -describe("fromArrayBuffer", () => { - it("throws if argument is not an ArrayBuffer", () => { - const input = 255; - // @ts-expect-error is not assignable to parameter of type 'ArrayBuffer' - expect(() => fromArrayBuffer(input)).toThrow( - new TypeError(`The "input" argument must be ArrayBuffer. Received type ${typeof input} (${input})`) - ); - }); - - describe("returns if argument is an ArrayBuffer", () => { - const buffer = new ArrayBuffer(16); - - it("with one arg", () => { - fromArrayBuffer(buffer); - expect(Buffer.from).toHaveBeenCalledTimes(1); - expect(Buffer.from).toHaveBeenCalledWith(buffer, 0, buffer.byteLength); - }); - - it("with two args", () => { - const offset = 12; - fromArrayBuffer(buffer, offset); - expect(Buffer.from).toHaveBeenCalledTimes(1); - expect(Buffer.from).toHaveBeenCalledWith(buffer, offset, buffer.byteLength - offset); - }); - - it("with three args", () => { - const offset = 12; - const length = 13; - fromArrayBuffer(buffer, offset, length); - expect(Buffer.from).toHaveBeenCalledTimes(1); - expect(Buffer.from).toHaveBeenCalledWith(buffer, offset, length); - }); - }); -}); - -describe("fromString", () => { - it("throws if argument is not an ArrayBuffer", () => { - const input = 255; - // @ts-expect-error is not assignable to parameter of type 'ArrayBuffer' - expect(() => fromString(input)).toThrow( - new TypeError(`The "input" argument must be of type string. Received type ${typeof input} (${input})`) - ); - }); - - describe("returns if argument is an ArrayBuffer", () => { - const input = "a string"; - - it("without explicit encoding", () => { - fromString(input); - expect(Buffer.from).toHaveBeenCalledTimes(1); - expect(Buffer.from).toHaveBeenCalledWith(input); - }); - - it("with encoding", () => { - const encoding = "utf16le"; - fromString(input, encoding); - expect(Buffer.from).toHaveBeenCalledTimes(1); - expect(Buffer.from).toHaveBeenCalledWith(input, encoding); - }); - }); -}); diff --git a/packages/util-buffer-from/src/index.ts b/packages/util-buffer-from/src/index.ts index 2633d10aa3ed..0e51df1539e8 100644 --- a/packages/util-buffer-from/src/index.ts +++ b/packages/util-buffer-from/src/index.ts @@ -1,29 +1 @@ -import { isArrayBuffer } from "@aws-sdk/is-array-buffer"; -import { Buffer } from "buffer"; - -/** - * @internal - */ -export const fromArrayBuffer = (input: ArrayBuffer, offset = 0, length: number = input.byteLength - offset): Buffer => { - if (!isArrayBuffer(input)) { - throw new TypeError(`The "input" argument must be ArrayBuffer. Received type ${typeof input} (${input})`); - } - - return Buffer.from(input, offset, length); -}; - -/** - * @internal - */ -export type StringEncoding = "ascii" | "utf8" | "utf16le" | "ucs2" | "base64" | "latin1" | "binary" | "hex"; - -/** - * @internal - */ -export const fromString = (input: string, encoding?: StringEncoding): Buffer => { - if (typeof input !== "string") { - throw new TypeError(`The "input" argument must be of type string. Received type ${typeof input} (${input})`); - } - - return encoding ? Buffer.from(input, encoding) : Buffer.from(input); -}; +export * from "@smithy/util-buffer-from"; diff --git a/packages/util-config-provider/jest.config.js b/packages/util-config-provider/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/util-config-provider/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/util-config-provider/package.json b/packages/util-config-provider/package.json index 8bd3cfa32ee3..2926007d3998 100644 --- a/packages/util-config-provider/package.json +++ b/packages/util-config-provider/package.json @@ -10,7 +10,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest --passWithNoTests" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", @@ -22,6 +22,7 @@ "module": "./dist-es/index.js", "types": "./dist-types/index.d.ts", "dependencies": { + "@smithy/util-config-provider": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/util-config-provider/src/booleanSelector.spec.ts b/packages/util-config-provider/src/booleanSelector.spec.ts deleted file mode 100644 index e2b264a74739..000000000000 --- a/packages/util-config-provider/src/booleanSelector.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { booleanSelector, SelectorType } from "./booleanSelector"; - -describe(booleanSelector.name, () => { - const key = "key"; - const obj: { [key]: any } = {} as any; - - describe.each(Object.entries(SelectorType))(`Selector %s`, (selectorKey, selectorValue) => { - beforeEach(() => { - delete obj[key]; - }); - - it(`should return undefined if ${key} is not defined`, () => { - expect(booleanSelector(obj, key, SelectorType[selectorKey])).toBeUndefined(); - }); - - it.each([ - [true, "true"], - [false, "false"], - ])(`should return boolean %s if ${key}="%s"`, (output, input) => { - obj[key] = input; - expect(booleanSelector(obj, key, SelectorType[selectorKey])).toBe(output); - }); - - it.each(["0", "1", "yes", "no", undefined, null, void 0, ""])(`should throw if ${key}=%s`, (input) => { - obj[key] = input; - expect(() => booleanSelector(obj, key, SelectorType[selectorKey])).toThrow( - `Cannot load ${selectorValue} "${key}". Expected "true" or "false", got ${obj[key]}.` - ); - }); - }); -}); diff --git a/packages/util-config-provider/src/booleanSelector.ts b/packages/util-config-provider/src/booleanSelector.ts deleted file mode 100644 index d8e16343d0e6..000000000000 --- a/packages/util-config-provider/src/booleanSelector.ts +++ /dev/null @@ -1,19 +0,0 @@ -export enum SelectorType { - ENV = "env", - CONFIG = "shared config entry", -} - -/** - * Returns boolean value true/false for string value "true"/"false", - * if the string is defined in obj[key] - * Returns undefined, if obj[key] is not defined. - * Throws error for all other cases. - * - * @internal - */ -export const booleanSelector = (obj: Record, key: string, type: SelectorType) => { - if (!(key in obj)) return undefined; - if (obj[key] === "true") return true; - if (obj[key] === "false") return false; - throw new Error(`Cannot load ${type} "${key}". Expected "true" or "false", got ${obj[key]}.`); -}; diff --git a/packages/util-config-provider/src/index.ts b/packages/util-config-provider/src/index.ts index 838aa5056195..621bc5cea6d8 100644 --- a/packages/util-config-provider/src/index.ts +++ b/packages/util-config-provider/src/index.ts @@ -1 +1 @@ -export * from "./booleanSelector"; +export * from "@smithy/util-config-provider"; diff --git a/packages/util-defaults-mode-browser/jest.config.js b/packages/util-defaults-mode-browser/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/util-defaults-mode-browser/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/util-defaults-mode-browser/package.json b/packages/util-defaults-mode-browser/package.json index 4d39296f09cd..8a08cf8da2b5 100644 --- a/packages/util-defaults-mode-browser/package.json +++ b/packages/util-defaults-mode-browser/package.json @@ -8,7 +8,7 @@ "build:include:deps": "lerna run --scope $npm_package_name --include-dependencies build", "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -19,8 +19,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/property-provider": "*", - "@aws-sdk/types": "*", + "@smithy/util-defaults-mode-browser": "^1.0.1", "bowser": "^2.11.0", "tslib": "^2.5.0" }, diff --git a/packages/util-defaults-mode-browser/src/constants.ts b/packages/util-defaults-mode-browser/src/constants.ts deleted file mode 100644 index 4e7e1106a0a1..000000000000 --- a/packages/util-defaults-mode-browser/src/constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { DefaultsMode } from "@aws-sdk/smithy-client"; -import type { Provider } from "@aws-sdk/types"; - -/** - * @internal - */ -export const DEFAULTS_MODE_OPTIONS = ["in-region", "cross-region", "mobile", "standard", "legacy"]; - -/** - * @internal - */ -export interface ResolveDefaultsModeConfigOptions { - defaultsMode?: DefaultsMode | Provider; -} diff --git a/packages/util-defaults-mode-browser/src/index.ts b/packages/util-defaults-mode-browser/src/index.ts index 003de26f83f2..e2813bc2738d 100644 --- a/packages/util-defaults-mode-browser/src/index.ts +++ b/packages/util-defaults-mode-browser/src/index.ts @@ -1,4 +1 @@ -/** - * @internal - */ -export * from "./resolveDefaultsModeConfig"; +export * from "@smithy/util-defaults-mode-browser"; diff --git a/packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.native.spec.ts b/packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.native.spec.ts deleted file mode 100644 index 80c0b1f1cf3a..000000000000 --- a/packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.native.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DEFAULTS_MODE_OPTIONS } from "./constants"; -import { resolveDefaultsModeConfig } from "./resolveDefaultsModeConfig.native"; - -describe("resolveDefaultsModeConfig", () => { - it("should default to legacy", async () => { - expect(await resolveDefaultsModeConfig({})()).toBe("legacy"); - expect(await resolveDefaultsModeConfig()()).toBe("legacy"); - }); - - it("should resolve auto to mobile", async () => { - expect(await resolveDefaultsModeConfig({ defaultsMode: "auto" })()).toBe("mobile"); - }); - - it.each(DEFAULTS_MODE_OPTIONS)("should resolve %s mode", async (mode) => { - expect(await resolveDefaultsModeConfig({ defaultsMode: () => Promise.resolve(mode as any) })()).toBe(mode); - }); - - it.each(["invalid", "abc"])("should throw for invalid value %s", async (mode) => { - try { - await resolveDefaultsModeConfig({ defaultsMode: () => Promise.resolve(mode as any) })(); - fail("should throw for invalid modes"); - } catch (e) { - expect(e.message).toContain("Invalid parameter"); - } - }); -}); diff --git a/packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.native.ts b/packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.native.ts deleted file mode 100644 index 9761808891e1..000000000000 --- a/packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.native.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { memoize } from "@aws-sdk/property-provider"; -import type { DefaultsMode, ResolvedDefaultsMode } from "@aws-sdk/smithy-client"; -import type { Provider } from "@aws-sdk/types"; - -import { DEFAULTS_MODE_OPTIONS } from "./constants"; - -/** - * @internal - */ -export interface ResolveDefaultsModeConfigOptions { - defaultsMode?: DefaultsMode | Provider; -} - -/** - * Validate the defaultsMode configuration. If the value is set to "auto", it - * resolves the value to "mobile". - * - * @default "legacy" - * @internal - */ -export const resolveDefaultsModeConfig = ({ - defaultsMode, -}: ResolveDefaultsModeConfigOptions = {}): Provider => - memoize(async () => { - const mode = typeof defaultsMode === "function" ? await defaultsMode() : defaultsMode; - switch (mode?.toLowerCase()) { - case "auto": - // Because this function is only exists in React Native, so it only resolves to "mobile" - // when defaultsMode set to "auto". - return Promise.resolve("mobile"); - case "mobile": - case "in-region": - case "cross-region": - case "standard": - case "legacy": - return Promise.resolve(mode?.toLocaleLowerCase() as ResolvedDefaultsMode); - case undefined: - return Promise.resolve("legacy"); - default: - throw new Error( - `Invalid parameter for "defaultsMode", expect ${DEFAULTS_MODE_OPTIONS.join(", ")}, got ${mode}` - ); - } - }); diff --git a/packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.spec.ts b/packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.spec.ts deleted file mode 100644 index 779f5dfcdb5f..000000000000 --- a/packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @jest-environment jsdom - */ -import bowser from "bowser"; - -import { DEFAULTS_MODE_OPTIONS } from "./constants"; -import { resolveDefaultsModeConfig } from "./resolveDefaultsModeConfig"; -jest.mock("bowser"); - -describe("resolveDefaultsModeConfig", () => { - const uaSpy = jest.spyOn(window.navigator, "userAgent", "get").mockReturnValue("some UA"); - - afterEach(() => { - uaSpy.mockClear(); - }); - - it("should default to legacy", async () => { - expect(await resolveDefaultsModeConfig({})()).toBe("legacy"); - expect(await resolveDefaultsModeConfig()()).toBe("legacy"); - }); - - it.each(DEFAULTS_MODE_OPTIONS)("should resolve %s mode", async (mode) => { - expect(await resolveDefaultsModeConfig({ defaultsMode: () => Promise.resolve(mode as any) })()).toBe(mode); - }); - - it("should resolve auto mode to mobile if platform is mobile", async () => { - (bowser.parse as jest.Mock).mockReturnValue({ platform: { type: "mobile" } }); - expect(await resolveDefaultsModeConfig({ defaultsMode: () => Promise.resolve("auto") })()).toBe("mobile"); - }); - - it("should resolve auto mode to mobile if platform is tablet", async () => { - (bowser.parse as jest.Mock).mockReturnValue({ platform: { type: "tablet" } }); - expect(await resolveDefaultsModeConfig({ defaultsMode: () => Promise.resolve("auto") })()).toBe("mobile"); - }); - - it("should resolve auto mode to standard if platform not mobile or tablet", async () => { - (bowser.parse as jest.Mock).mockReturnValue({ platform: { type: "desktop" } }); - expect(await resolveDefaultsModeConfig({ defaultsMode: () => Promise.resolve("auto") })()).toBe("standard"); - }); - - it("should memoize the response", async () => { - const defaultsMode = resolveDefaultsModeConfig({ defaultsMode: () => Promise.resolve("auto") }); - await defaultsMode(); - const spyInvokeCount = uaSpy.mock.calls.length; - await defaultsMode(); - expect(uaSpy).toBeCalledTimes(spyInvokeCount); - }); - - it.each(["invalid", "abc"])("should throw for invalid value %s", async (mode) => { - try { - await resolveDefaultsModeConfig({ defaultsMode: () => Promise.resolve(mode as any) })(); - fail("should throw for invalid modes"); - } catch (e) { - expect(e.message).toContain("Invalid parameter"); - } - }); -}); diff --git a/packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.ts b/packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.ts deleted file mode 100644 index 58f65d347867..000000000000 --- a/packages/util-defaults-mode-browser/src/resolveDefaultsModeConfig.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { memoize } from "@aws-sdk/property-provider"; -import type { DefaultsMode, ResolvedDefaultsMode } from "@aws-sdk/smithy-client"; -import type { Provider } from "@aws-sdk/types"; -import bowser from "bowser"; - -import { DEFAULTS_MODE_OPTIONS } from "./constants"; - -/** - * @internal - */ -export interface ResolveDefaultsModeConfigOptions { - defaultsMode?: DefaultsMode | Provider; -} - -/** - * Validate the defaultsMode configuration. If the value is set to "auto", it - * resolves the value to "mobile" if the app is running in a mobile browser, - * otherwise it resolves to "standard". - * - * @default "legacy" - * @internal - */ -export const resolveDefaultsModeConfig = ({ - defaultsMode, -}: ResolveDefaultsModeConfigOptions = {}): Provider => - memoize(async () => { - const mode = typeof defaultsMode === "function" ? await defaultsMode() : defaultsMode; - switch (mode?.toLowerCase()) { - case "auto": - return Promise.resolve(isMobileBrowser() ? "mobile" : "standard"); - case "mobile": - case "in-region": - case "cross-region": - case "standard": - case "legacy": - return Promise.resolve(mode?.toLocaleLowerCase() as ResolvedDefaultsMode); - case undefined: - return Promise.resolve("legacy"); - default: - throw new Error( - `Invalid parameter for "defaultsMode", expect ${DEFAULTS_MODE_OPTIONS.join(", ")}, got ${mode}` - ); - } - }); - -const isMobileBrowser = (): boolean => { - const parsedUA = - typeof window !== "undefined" && window?.navigator?.userAgent - ? bowser.parse(window.navigator.userAgent) - : undefined; - const platform = parsedUA?.platform?.type; - // Reference: https://github.com/lancedikson/bowser/blob/master/src/constants.js#L86 - return platform === "tablet" || platform === "mobile"; -}; diff --git a/packages/util-defaults-mode-node/jest.config.js b/packages/util-defaults-mode-node/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/util-defaults-mode-node/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/util-defaults-mode-node/package.json b/packages/util-defaults-mode-node/package.json index 7c4361e27729..128b1580225e 100644 --- a/packages/util-defaults-mode-node/package.json +++ b/packages/util-defaults-mode-node/package.json @@ -8,7 +8,7 @@ "build:include:deps": "lerna run --scope $npm_package_name --include-dependencies build", "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -19,11 +19,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/config-resolver": "*", - "@aws-sdk/credential-provider-imds": "*", - "@aws-sdk/node-config-provider": "*", - "@aws-sdk/property-provider": "*", - "@aws-sdk/types": "*", + "@smithy/util-defaults-mode-node": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/util-defaults-mode-node/src/constants.ts b/packages/util-defaults-mode-node/src/constants.ts deleted file mode 100644 index 4ba284d6c36b..000000000000 --- a/packages/util-defaults-mode-node/src/constants.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @internal - */ -export const AWS_EXECUTION_ENV = "AWS_EXECUTION_ENV"; -/** - * @internal - */ -export const AWS_REGION_ENV = "AWS_REGION"; -/** - * @internal - */ -export const AWS_DEFAULT_REGION_ENV = "AWS_DEFAULT_REGION"; -/** - * @internal - */ -export const ENV_IMDS_DISABLED = "AWS_EC2_METADATA_DISABLED"; -/** - * @internal - */ -export const DEFAULTS_MODE_OPTIONS = ["in-region", "cross-region", "mobile", "standard", "legacy"]; -/** - * @internal - */ -export const IMDS_REGION_PATH = "/latest/meta-data/placement/region"; diff --git a/packages/util-defaults-mode-node/src/defaultsModeConfig.ts b/packages/util-defaults-mode-node/src/defaultsModeConfig.ts deleted file mode 100644 index 7d8c9b1638ac..000000000000 --- a/packages/util-defaults-mode-node/src/defaultsModeConfig.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { LoadedConfigSelectors } from "@aws-sdk/node-config-provider"; -import type { DefaultsMode } from "@aws-sdk/smithy-client"; - -const AWS_DEFAULTS_MODE_ENV = "AWS_DEFAULTS_MODE"; -const AWS_DEFAULTS_MODE_CONFIG = "defaults_mode"; - -/** - * @internal - */ -export const NODE_DEFAULTS_MODE_CONFIG_OPTIONS: LoadedConfigSelectors = { - environmentVariableSelector: (env) => { - return env[AWS_DEFAULTS_MODE_ENV] as DefaultsMode; - }, - configFileSelector: (profile) => { - return profile[AWS_DEFAULTS_MODE_CONFIG] as DefaultsMode; - }, - default: "legacy", -}; diff --git a/packages/util-defaults-mode-node/src/index.ts b/packages/util-defaults-mode-node/src/index.ts index 003de26f83f2..0287eb45100f 100644 --- a/packages/util-defaults-mode-node/src/index.ts +++ b/packages/util-defaults-mode-node/src/index.ts @@ -1,4 +1 @@ -/** - * @internal - */ -export * from "./resolveDefaultsModeConfig"; +export * from "@smithy/util-defaults-mode-node"; diff --git a/packages/util-defaults-mode-node/src/resolveDefaultsModeConfig.spec.ts b/packages/util-defaults-mode-node/src/resolveDefaultsModeConfig.spec.ts deleted file mode 100644 index 91ac219a1c26..000000000000 --- a/packages/util-defaults-mode-node/src/resolveDefaultsModeConfig.spec.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { NODE_REGION_CONFIG_OPTIONS } from "@aws-sdk/config-resolver"; -import * as ImdsProvider from "@aws-sdk/credential-provider-imds"; -import * as NodeConfigProvider from "@aws-sdk/node-config-provider"; - -import { - AWS_DEFAULT_REGION_ENV, - AWS_EXECUTION_ENV, - AWS_REGION_ENV, - DEFAULTS_MODE_OPTIONS, - ENV_IMDS_DISABLED, - IMDS_REGION_PATH, -} from "./constants"; -import { NODE_DEFAULTS_MODE_CONFIG_OPTIONS } from "./defaultsModeConfig"; -import { resolveDefaultsModeConfig } from "./resolveDefaultsModeConfig"; - -jest.mock("@aws-sdk/node-config-provider"); -jest.mock("@aws-sdk/credential-provider-imds"); - -describe("resolveDefaultsModeConfig", () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it("should default to legacy", async () => { - expect(await resolveDefaultsModeConfig({})()).toBe("legacy"); - expect(await resolveDefaultsModeConfig()()).toBe("legacy"); - }); - - it.each(DEFAULTS_MODE_OPTIONS)("should resolve %s mode", async (mode) => { - expect(await resolveDefaultsModeConfig({ defaultsMode: () => Promise.resolve(mode as any) })()).toBe(mode); - }); - - it.each(["invalid", "abc"])("should throw for invalid value %s", async (mode) => { - try { - await resolveDefaultsModeConfig({ defaultsMode: () => Promise.resolve(mode as any) })(); - fail("should throw for invalid modes"); - } catch (e) { - expect(e.message).toContain("Invalid parameter"); - } - }); - - it("should memoize the response", async () => { - const providerMock = jest.fn().mockResolvedValue("legacy"); - const defaultsMode = resolveDefaultsModeConfig({ defaultsMode: providerMock }); - await defaultsMode(); - const mockInvokeCount = providerMock.mock.calls.length; - await defaultsMode(); - expect(providerMock).toBeCalledTimes(mockInvokeCount); - }); - - it("should resolve client region from Node config provider chain", async () => { - const loadConfigMock = NodeConfigProvider.loadConfig as jest.Mock; - loadConfigMock.mockReturnValueOnce(undefined); - expect(await resolveDefaultsModeConfig({ defaultsMode: () => Promise.resolve("mobile") })()).toBe("mobile"); - expect(loadConfigMock.mock.calls[0][0]).toBe(NODE_REGION_CONFIG_OPTIONS); - }); - - it("should resolve defaults mode from Node config provider chain", async () => { - const loadConfigMock = NodeConfigProvider.loadConfig as jest.Mock; - loadConfigMock.mockReturnValueOnce("us-west-2").mockReturnValueOnce("mobile"); - expect(await resolveDefaultsModeConfig({})()).toBe("mobile"); - expect(loadConfigMock.mock.calls[1][0]).toBe(NODE_DEFAULTS_MODE_CONFIG_OPTIONS); - }); - - describe("auto mode inference", () => { - const originalEnv = process.env; - beforeEach(() => { - process.env = {}; - }); - - afterEach(() => { - process.env = originalEnv; - }); - - it("should use the AWS_REGION env when in an AWS service environment", async () => { - process.env[AWS_EXECUTION_ENV] = "aws-lambda"; - process.env[AWS_REGION_ENV] = "us-west-2"; - expect(await resolveDefaultsModeConfig({ region: "us-west-1", defaultsMode: "auto" })()).toBe("cross-region"); - process.env[AWS_REGION_ENV] = "us-west-1"; - expect(await resolveDefaultsModeConfig({ region: "us-west-1", defaultsMode: "auto" })()).toBe("in-region"); - }); - - it("should use the AWS_DEFAULT_REGION env when in an AWS service environment", async () => { - process.env[AWS_EXECUTION_ENV] = "aws-lambda"; - process.env[AWS_DEFAULT_REGION_ENV] = "us-west-2"; - expect(await resolveDefaultsModeConfig({ region: "us-west-1", defaultsMode: "auto" })()).toBe("cross-region"); - process.env[AWS_DEFAULT_REGION_ENV] = "us-west-1"; - expect(await resolveDefaultsModeConfig({ region: "us-west-1", defaultsMode: "auto" })()).toBe("in-region"); - }); - - it("should make request to IMDS endpoint to resolve client region", async () => { - const fakeImdsEndpoint = { path: "foo", hostname: "bar" }; - const getImdsEndpointMock = (ImdsProvider.getInstanceMetadataEndpoint as jest.Mock).mockResolvedValue( - fakeImdsEndpoint - ); - const httpRequestMock = (ImdsProvider.httpRequest as jest.Mock).mockResolvedValue("us-west-2"); - expect(await resolveDefaultsModeConfig({ region: "us-west-1", defaultsMode: "auto" })()).toBe("cross-region"); - expect(getImdsEndpointMock).toBeCalled(); - expect(httpRequestMock.mock.calls[0][0]).toMatchObject({ ...fakeImdsEndpoint, path: IMDS_REGION_PATH }); - httpRequestMock.mockResolvedValue("us-west-1"); - expect(await resolveDefaultsModeConfig({ region: "us-west-1", defaultsMode: "auto" })()).toBe("in-region"); - }); - - it(`should skip calling IMDS if ${ENV_IMDS_DISABLED} is set`, async () => { - process.env[ENV_IMDS_DISABLED] = "true"; - const getImdsEndpointMock = ImdsProvider.getInstanceMetadataEndpoint as jest.Mock; - const httpRequestMock = ImdsProvider.httpRequest as jest.Mock; - expect(await resolveDefaultsModeConfig({ region: "us-west-1", defaultsMode: "auto" })()).toBe("standard"); - expect(getImdsEndpointMock).not.toBeCalled(); - expect(httpRequestMock).not.toBeCalled(); - }); - }); -}); diff --git a/packages/util-defaults-mode-node/src/resolveDefaultsModeConfig.ts b/packages/util-defaults-mode-node/src/resolveDefaultsModeConfig.ts deleted file mode 100644 index de14b0e88e9c..000000000000 --- a/packages/util-defaults-mode-node/src/resolveDefaultsModeConfig.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { NODE_REGION_CONFIG_OPTIONS } from "@aws-sdk/config-resolver"; -import { getInstanceMetadataEndpoint, httpRequest } from "@aws-sdk/credential-provider-imds"; -import { loadConfig } from "@aws-sdk/node-config-provider"; -import { memoize } from "@aws-sdk/property-provider"; -import type { DefaultsMode, ResolvedDefaultsMode } from "@aws-sdk/smithy-client"; -import type { Provider } from "@aws-sdk/types"; - -import { - AWS_DEFAULT_REGION_ENV, - AWS_EXECUTION_ENV, - AWS_REGION_ENV, - DEFAULTS_MODE_OPTIONS, - ENV_IMDS_DISABLED, - IMDS_REGION_PATH, -} from "./constants"; -import { NODE_DEFAULTS_MODE_CONFIG_OPTIONS } from "./defaultsModeConfig"; - -/** - * @internal - */ -export interface ResolveDefaultsModeConfigOptions { - defaultsMode?: DefaultsMode | Provider; - region?: string | Provider; -} - -/** - * Validate the defaultsMode configuration. If the value is set to "auto", it - * resolves the value to "in-region", "cross-region", or "standard". - * - * @default "legacy" - * @internal - */ -export const resolveDefaultsModeConfig = ({ - region = loadConfig(NODE_REGION_CONFIG_OPTIONS), - defaultsMode = loadConfig(NODE_DEFAULTS_MODE_CONFIG_OPTIONS), -}: ResolveDefaultsModeConfigOptions = {}): Provider => - memoize(async () => { - const mode = typeof defaultsMode === "function" ? await defaultsMode() : defaultsMode; - switch (mode?.toLowerCase()) { - case "auto": - return resolveNodeDefaultsModeAuto(region); - case "in-region": - case "cross-region": - case "mobile": - case "standard": - case "legacy": - return Promise.resolve(mode?.toLocaleLowerCase() as ResolvedDefaultsMode); - case undefined: - return Promise.resolve("legacy"); - default: - throw new Error( - `Invalid parameter for "defaultsMode", expect ${DEFAULTS_MODE_OPTIONS.join(", ")}, got ${mode}` - ); - } - }); - -const resolveNodeDefaultsModeAuto = async (clientRegion?: string | Provider): Promise => { - if (clientRegion) { - const resolvedRegion = typeof clientRegion === "function" ? await clientRegion() : clientRegion; - const inferredRegion = await inferPhysicalRegion(); - if (!inferredRegion) { - return "standard"; - } - if (resolvedRegion === inferredRegion) { - return "in-region"; - } else { - return "cross-region"; - } - } - return "standard"; -}; - -/** - * Infer the hosting app's physical region. - */ -const inferPhysicalRegion = async (): Promise => { - if (process.env[AWS_EXECUTION_ENV] && (process.env[AWS_REGION_ENV] || process.env[AWS_DEFAULT_REGION_ENV])) { - // We're running in an AWS service environment, so we can trust the region environment variables to be the current - // region, if they're set - return process.env[AWS_REGION_ENV] ?? process.env[AWS_DEFAULT_REGION_ENV]; - } - if (!process.env[ENV_IMDS_DISABLED]) { - // We couldn't figure out the region from environment variables. Check IMDSv2 - try { - const endpoint = await getInstanceMetadataEndpoint(); - return (await httpRequest({ ...endpoint, path: IMDS_REGION_PATH })).toString(); - } catch (e) { - // Swallow the error. - } - } -}; diff --git a/packages/util-hex-encoding/jest.config.js b/packages/util-hex-encoding/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/util-hex-encoding/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/util-hex-encoding/package.json b/packages/util-hex-encoding/package.json index 41525042d23f..11a49e77147e 100644 --- a/packages/util-hex-encoding/package.json +++ b/packages/util-hex-encoding/package.json @@ -10,7 +10,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", @@ -20,6 +20,7 @@ "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", "dependencies": { + "@smithy/util-hex-encoding": "^1.0.1", "tslib": "^2.5.0" }, "types": "./dist-types/index.d.ts", diff --git a/packages/util-hex-encoding/src/index.spec.ts b/packages/util-hex-encoding/src/index.spec.ts deleted file mode 100644 index f99f897ad049..000000000000 --- a/packages/util-hex-encoding/src/index.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { fromHex, toHex } from "./"; - -const encoded = "dead" + "beef" + "cafe" + "babe" + "face"; -const bytes = new Uint8Array([0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce]); - -describe("fromHex", () => { - it("should decode hexadecimal strings to binary", () => { - expect(fromHex(encoded)).toEqual(bytes); - }); - - it("should throw if the string is not an even number of code units", () => { - expect(() => fromHex(encoded + "a")).toThrow(); - }); - - it("should throw if an unexpected sequence is encountered", () => { - expect(() => fromHex("xy")).toThrow(); - }); - - it("should decode hexadecimal strings regardless of casing", () => { - expect(fromHex(encoded.toLowerCase())).toEqual(bytes); - expect(fromHex(encoded.toUpperCase())).toEqual(bytes); - }); -}); - -describe("toHex", () => { - it("should encode bytes as hexadecimal", () => { - expect(toHex(bytes)).toBe(encoded); - }); -}); diff --git a/packages/util-hex-encoding/src/index.ts b/packages/util-hex-encoding/src/index.ts index bc09b3b0d884..bbf978d8ce96 100644 --- a/packages/util-hex-encoding/src/index.ts +++ b/packages/util-hex-encoding/src/index.ts @@ -1,49 +1 @@ -const SHORT_TO_HEX: { [key: number]: string } = {}; -const HEX_TO_SHORT: Record = {}; - -for (let i = 0; i < 256; i++) { - let encodedByte = i.toString(16).toLowerCase(); - if (encodedByte.length === 1) { - encodedByte = `0${encodedByte}`; - } - - SHORT_TO_HEX[i] = encodedByte; - HEX_TO_SHORT[encodedByte] = i; -} - -/** - * Converts a hexadecimal encoded string to a Uint8Array of bytes. - * - * @param encoded The hexadecimal encoded string - */ -export function fromHex(encoded: string): Uint8Array { - if (encoded.length % 2 !== 0) { - throw new Error("Hex encoded strings must have an even number length"); - } - - const out = new Uint8Array(encoded.length / 2); - for (let i = 0; i < encoded.length; i += 2) { - const encodedByte = encoded.slice(i, i + 2).toLowerCase(); - if (encodedByte in HEX_TO_SHORT) { - out[i / 2] = HEX_TO_SHORT[encodedByte]; - } else { - throw new Error(`Cannot decode unrecognized sequence ${encodedByte} as hexadecimal`); - } - } - - return out; -} - -/** - * Converts a Uint8Array of binary data to a hexadecimal encoded string. - * - * @param bytes The binary data to encode - */ -export function toHex(bytes: Uint8Array): string { - let out = ""; - for (let i = 0; i < bytes.byteLength; i++) { - out += SHORT_TO_HEX[bytes[i]]; - } - - return out; -} +export * from "@smithy/util-hex-encoding"; diff --git a/packages/util-middleware/jest.config.js b/packages/util-middleware/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/util-middleware/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/util-middleware/package.json b/packages/util-middleware/package.json index 7ff9c65d71d7..6af14ee6f168 100644 --- a/packages/util-middleware/package.json +++ b/packages/util-middleware/package.json @@ -12,7 +12,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "keywords": [ "aws", @@ -24,6 +24,7 @@ }, "license": "Apache-2.0", "dependencies": { + "@smithy/util-middleware": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/util-middleware/src/index.ts b/packages/util-middleware/src/index.ts index 37e779c5d532..84acc66996cf 100644 --- a/packages/util-middleware/src/index.ts +++ b/packages/util-middleware/src/index.ts @@ -1,4 +1 @@ -/** - * @internal - */ -export * from "./normalizeProvider"; +export * from "@smithy/util-middleware"; diff --git a/packages/util-middleware/src/normalizeProvider.spec.ts b/packages/util-middleware/src/normalizeProvider.spec.ts deleted file mode 100644 index 8650d6f0b246..000000000000 --- a/packages/util-middleware/src/normalizeProvider.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { normalizeProvider } from "./normalizeProvider"; - -describe(normalizeProvider.name, () => { - const testCases = [ - true, // boolean - null, // null - undefined, // undefined - 1, // number - "", // string - {}, // object - ]; - - it.each(testCases)("returns Provider if value is not a function: %s", async (value) => { - const output = normalizeProvider(value); - expect(await output()).toEqual(value); - }); - - it.each(testCases)("returns Provider if value if a function which returns %s", (value) => { - const mockValueProvider = () => Promise.resolve(value); - expect(normalizeProvider(mockValueProvider)).toBe(mockValueProvider); - }); -}); diff --git a/packages/util-middleware/src/normalizeProvider.ts b/packages/util-middleware/src/normalizeProvider.ts deleted file mode 100644 index 04d27bd09432..000000000000 --- a/packages/util-middleware/src/normalizeProvider.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Provider } from "@aws-sdk/types"; - -/** - * @internal - * - * @returns a provider function for the input value if it isn't already one. - */ -export const normalizeProvider = (input: T | Provider): Provider => { - if (typeof input === "function") return input as Provider; - const promisified = Promise.resolve(input); - return () => promisified; -}; diff --git a/packages/util-retry/jest.config.js b/packages/util-retry/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/util-retry/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/util-retry/package.json b/packages/util-retry/package.json index 7abec0f82bde..e6c91f28e127 100644 --- a/packages/util-retry/package.json +++ b/packages/util-retry/package.json @@ -13,7 +13,7 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "extract:docs": "api-extractor run --local", - "test": "jest" + "test": "exit 0" }, "keywords": [ "aws", @@ -25,7 +25,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/service-error-classification": "*", + "@smithy/util-retry": "^1.0.3", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/util-retry/src/AdaptiveRetryStrategy.spec.ts b/packages/util-retry/src/AdaptiveRetryStrategy.spec.ts deleted file mode 100644 index 27220d4ca084..000000000000 --- a/packages/util-retry/src/AdaptiveRetryStrategy.spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { RetryErrorInfo, StandardRetryToken } from "@aws-sdk/types"; - -import { AdaptiveRetryStrategy } from "./AdaptiveRetryStrategy"; -import { RETRY_MODES } from "./config"; -import { DefaultRateLimiter } from "./DefaultRateLimiter"; -import { StandardRetryStrategy } from "./StandardRetryStrategy"; -import { RateLimiter } from "./types"; - -jest.mock("./StandardRetryStrategy"); -jest.mock("./DefaultRateLimiter"); - -describe(AdaptiveRetryStrategy.name, () => { - const maxAttemptsProvider = jest.fn(); - const retryTokenScope = "scope"; - const mockDefaultRateLimiter = { - getSendToken: jest.fn(), - updateClientSendingRate: jest.fn(), - }; - const mockRetryToken: StandardRetryToken = { - getRetryCost: () => 1, - getRetryCount: () => 1, - getRetryDelay: () => 1, - }; - const errorInfo = { - errorType: "TRANSIENT", - } as RetryErrorInfo; - - beforeEach(() => { - (DefaultRateLimiter as jest.Mock).mockReturnValue(mockDefaultRateLimiter); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it(`sets mode=${RETRY_MODES.ADAPTIVE}`, () => { - const retryStrategy = new AdaptiveRetryStrategy(maxAttemptsProvider); - expect(retryStrategy.mode).toStrictEqual(RETRY_MODES.ADAPTIVE); - }); - - describe("rateLimiter init", () => { - it("sets getDefaultrateLimiter if options is undefined", () => { - const retryStrategy = new AdaptiveRetryStrategy(maxAttemptsProvider); - expect(retryStrategy["rateLimiter"]).toBe(mockDefaultRateLimiter); - }); - - it("sets DefaultRateLimiter if options.rateLimiter undefined", () => { - const retryStrategy = new AdaptiveRetryStrategy(maxAttemptsProvider, {}); - expect(retryStrategy["rateLimiter"]).toBe(mockDefaultRateLimiter); - }); - - it("sets options.rateLimiter if defined", () => { - const rateLimiter = {} as RateLimiter; - const retryStrategy = new AdaptiveRetryStrategy(maxAttemptsProvider, { - rateLimiter, - }); - expect(retryStrategy["rateLimiter"]).toBe(rateLimiter); - }); - }); - - describe("acquireInitialRetryToken", () => { - it("calls rateLimiter.getSendToken and returns initial retry token ", async () => { - const mockedStandardRetryStrategy = jest.spyOn(StandardRetryStrategy.prototype, "acquireInitialRetryToken"); - mockedStandardRetryStrategy.mockResolvedValue(mockRetryToken); - const retryStrategy = new AdaptiveRetryStrategy(maxAttemptsProvider, { - rateLimiter: mockDefaultRateLimiter, - }); - const token = await retryStrategy.acquireInitialRetryToken(retryTokenScope); - expect(mockDefaultRateLimiter.getSendToken).toHaveBeenCalledTimes(1); - expect(mockedStandardRetryStrategy).toHaveBeenCalledTimes(1); - expect(token).toStrictEqual(mockRetryToken); - }); - }); - describe("refreshRetryTokenForRetry", () => { - it("calls rateLimiter.updateCientSendingRate and refreshes retry token", async () => { - const mockedStandardRetryStrategy = jest.spyOn(StandardRetryStrategy.prototype, "refreshRetryTokenForRetry"); - mockedStandardRetryStrategy.mockResolvedValue(mockRetryToken); - const retryStrategy = new AdaptiveRetryStrategy(maxAttemptsProvider, { - rateLimiter: mockDefaultRateLimiter, - }); - const token = await retryStrategy.refreshRetryTokenForRetry(mockRetryToken, errorInfo); - expect(mockDefaultRateLimiter.updateClientSendingRate).toHaveBeenCalledTimes(1); - expect(mockDefaultRateLimiter.updateClientSendingRate).toHaveBeenCalledWith(errorInfo); - expect(mockedStandardRetryStrategy).toHaveBeenCalledTimes(1); - expect(mockedStandardRetryStrategy).toHaveBeenCalledWith(mockRetryToken, errorInfo); - expect(token).toStrictEqual(mockRetryToken); - }); - }); - describe("recordSuccess", () => { - it("rateLimiter.updateCientSendingRate and records success on token", async () => { - const mockedStandardRetryStrategy = jest.spyOn(StandardRetryStrategy.prototype, "recordSuccess"); - const retryStrategy = new AdaptiveRetryStrategy(maxAttemptsProvider, { - rateLimiter: mockDefaultRateLimiter, - }); - retryStrategy.recordSuccess(mockRetryToken); - expect(mockDefaultRateLimiter.updateClientSendingRate).toHaveBeenCalledTimes(1); - expect(mockDefaultRateLimiter.updateClientSendingRate).toHaveBeenCalledWith({}); - expect(mockedStandardRetryStrategy).toHaveBeenCalledTimes(1); - expect(mockedStandardRetryStrategy).toHaveBeenCalledWith(mockRetryToken); - }); - }); -}); diff --git a/packages/util-retry/src/AdaptiveRetryStrategy.ts b/packages/util-retry/src/AdaptiveRetryStrategy.ts deleted file mode 100644 index 313cc9174d68..000000000000 --- a/packages/util-retry/src/AdaptiveRetryStrategy.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Provider, RetryErrorInfo, RetryStrategyV2, RetryToken, StandardRetryToken } from "@aws-sdk/types"; - -import { RETRY_MODES } from "./config"; -import { DefaultRateLimiter } from "./DefaultRateLimiter"; -import { StandardRetryStrategy } from "./StandardRetryStrategy"; -import { RateLimiter } from "./types"; - -/** - * @public - * - * Strategy options to be passed to AdaptiveRetryStrategy - */ -export interface AdaptiveRetryStrategyOptions { - rateLimiter?: RateLimiter; -} - -/** - * @public - * - * The AdaptiveRetryStrategy is a retry strategy for executing against a very - * resource constrained set of resources. Care should be taken when using this - * retry strategy. By default, it uses a dynamic backoff delay based on load - * currently perceived against the downstream resource and performs circuit - * breaking to disable retries in the event of high downstream failures using - * the DefaultRateLimiter. - * - * @see {@link StandardRetryStrategy} - * @see {@link DefaultRateLimiter } - */ -export class AdaptiveRetryStrategy implements RetryStrategyV2 { - private rateLimiter: RateLimiter; - private standardRetryStrategy: StandardRetryStrategy; - public readonly mode: string = RETRY_MODES.ADAPTIVE; - - constructor(private readonly maxAttemptsProvider: Provider, options?: AdaptiveRetryStrategyOptions) { - const { rateLimiter } = options ?? {}; - this.rateLimiter = rateLimiter ?? new DefaultRateLimiter(); - this.standardRetryStrategy = new StandardRetryStrategy(maxAttemptsProvider); - } - - public async acquireInitialRetryToken(retryTokenScope: string): Promise { - await this.rateLimiter.getSendToken(); - return this.standardRetryStrategy.acquireInitialRetryToken(retryTokenScope); - } - - public async refreshRetryTokenForRetry( - tokenToRenew: StandardRetryToken, - errorInfo: RetryErrorInfo - ): Promise { - this.rateLimiter.updateClientSendingRate(errorInfo); - return this.standardRetryStrategy.refreshRetryTokenForRetry(tokenToRenew, errorInfo); - } - - public recordSuccess(token: StandardRetryToken): void { - this.rateLimiter.updateClientSendingRate({}); - this.standardRetryStrategy.recordSuccess(token); - } -} diff --git a/packages/util-retry/src/ConfiguredRetryStrategy.spec.ts b/packages/util-retry/src/ConfiguredRetryStrategy.spec.ts deleted file mode 100644 index 9a1dd3ea3a88..000000000000 --- a/packages/util-retry/src/ConfiguredRetryStrategy.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ConfiguredRetryStrategy } from "./ConfiguredRetryStrategy"; - -describe(ConfiguredRetryStrategy.name, () => { - it("allows setting a custom backoff function", async () => { - const strategy = new ConfiguredRetryStrategy(5, (attempt) => attempt * 1000); - - const token = await strategy.acquireInitialRetryToken(""); - token.getRetryCount = () => 3; - - const retryToken = await strategy.refreshRetryTokenForRetry(token, { - errorType: "TRANSIENT", - }); - - expect(retryToken.getRetryCount()).toBe(4); - expect(retryToken.getRetryDelay()).toBe(4000); - }); -}); diff --git a/packages/util-retry/src/ConfiguredRetryStrategy.ts b/packages/util-retry/src/ConfiguredRetryStrategy.ts deleted file mode 100644 index dea1c5da973d..000000000000 --- a/packages/util-retry/src/ConfiguredRetryStrategy.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { - Provider, - RetryBackoffStrategy, - RetryErrorInfo, - RetryStrategyV2, - StandardRetryToken, -} from "@aws-sdk/types"; - -import { DEFAULT_RETRY_DELAY_BASE } from "./constants"; -import { StandardRetryStrategy } from "./StandardRetryStrategy"; - -/** - * @public - * - * This extension of the StandardRetryStrategy allows customizing the - * backoff computation. - */ -export class ConfiguredRetryStrategy extends StandardRetryStrategy implements RetryStrategyV2 { - private readonly computeNextBackoffDelay: (attempt: number) => number; - /** - * @param maxAttempts - the maximum number of retry attempts allowed. - * e.g., if set to 3, then 4 total requests are possible. - * @param computeNextBackoffDelay - a millisecond delay for each retry or a function that takes the retry attempt - * and returns the delay. - * - * @example exponential backoff. - * ```js - * new Client({ - * retryStrategy: new ConfiguredRetryStrategy(3, (attempt) => attempt ** 2) - * }); - * ``` - * @example constant delay. - * ```js - * new Client({ - * retryStrategy: new ConfiguredRetryStrategy(3, 2000) - * }); - * ``` - */ - public constructor( - maxAttempts: number | Provider, - computeNextBackoffDelay: number | RetryBackoffStrategy["computeNextBackoffDelay"] = DEFAULT_RETRY_DELAY_BASE - ) { - super(typeof maxAttempts === "function" ? maxAttempts : async () => maxAttempts); - if (typeof computeNextBackoffDelay === "number") { - this.computeNextBackoffDelay = () => computeNextBackoffDelay; - } else { - this.computeNextBackoffDelay = computeNextBackoffDelay; - } - } - - public async refreshRetryTokenForRetry( - tokenToRenew: StandardRetryToken, - errorInfo: RetryErrorInfo - ): Promise { - const token = await super.refreshRetryTokenForRetry(tokenToRenew, errorInfo); - token.getRetryDelay = () => this.computeNextBackoffDelay(token.getRetryCount()); - return token; - } -} diff --git a/packages/util-retry/src/DefaultRateLimiter.spec.ts b/packages/util-retry/src/DefaultRateLimiter.spec.ts deleted file mode 100644 index a548c48c3ac7..000000000000 --- a/packages/util-retry/src/DefaultRateLimiter.spec.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { isThrottlingError } from "@aws-sdk/service-error-classification"; - -import { DefaultRateLimiter } from "./DefaultRateLimiter"; - -jest.mock("@aws-sdk/service-error-classification"); - -describe(DefaultRateLimiter.name, () => { - beforeEach(() => { - (isThrottlingError as jest.Mock).mockReturnValue(false); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("getSendToken", () => { - beforeEach(() => { - jest.useFakeTimers({ legacyFakeTimers: true }); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - it.each([ - [0.5, 892.8571428571428], - [1, 1785.7142857142856], - [2, 2000], - ])("timestamp: %d, delay: %d", async (timestamp, delay) => { - jest.spyOn(Date, "now").mockImplementation(() => 0); - const rateLimiter = new DefaultRateLimiter(); - - (isThrottlingError as jest.Mock).mockReturnValueOnce(true); - jest.spyOn(Date, "now").mockImplementation(() => timestamp * 1000); - rateLimiter.updateClientSendingRate({}); - - rateLimiter.getSendToken(); - jest.runAllTimers(); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), delay); - }); - }); - - describe("cubicSuccess", () => { - it.each([ - [5, 7], - [6, 9.64893601], - [7, 10.00003085], - [8, 10.45328452], - [9, 13.40869703], - [10, 21.26626836], - [11, 36.42599853], - ])("timestamp: %d, calculatedRate: %d", (timestamp, calculatedRate) => { - jest.spyOn(Date, "now").mockImplementation(() => 0); - const rateLimiter = new DefaultRateLimiter(); - rateLimiter["lastMaxRate"] = 10; - rateLimiter["lastThrottleTime"] = 5; - - jest.spyOn(Date, "now").mockImplementation(() => timestamp * 1000); - - const cubicSuccessSpy = jest.spyOn(DefaultRateLimiter.prototype as any, "cubicSuccess"); - rateLimiter.updateClientSendingRate({}); - expect(cubicSuccessSpy).toHaveLastReturnedWith(calculatedRate); - }); - }); - - describe("cubicThrottle", () => { - it.each([ - [5, 0.112], - [6, 0.09333333], - [7, 0.08], - [8, 0.07], - [9, 0.06222222], - ])("timestamp: %d, calculatedRate: %d", (timestamp, calculatedRate) => { - jest.spyOn(Date, "now").mockImplementation(() => 0); - const rateLimiter = new DefaultRateLimiter(); - rateLimiter["lastMaxRate"] = 10; - rateLimiter["lastThrottleTime"] = 5; - - (isThrottlingError as jest.Mock).mockReturnValueOnce(true); - jest.spyOn(Date, "now").mockImplementation(() => timestamp * 1000); - const cubicThrottleSpy = jest.spyOn(DefaultRateLimiter.prototype as any, "cubicThrottle"); - rateLimiter.updateClientSendingRate({}); - expect(cubicThrottleSpy).toHaveLastReturnedWith(calculatedRate); - }); - }); - - it("updateClientSendingRate", () => { - jest.spyOn(Date, "now").mockImplementation(() => 0); - const rateLimiter = new DefaultRateLimiter(); - - const testCases: [boolean, number, number, number][] = [ - [false, 0.2, 0, 0.5], - [false, 0.4, 0, 0.5], - [false, 0.6, 4.8, 0.5], - [false, 0.8, 4.8, 0.5], - [false, 1, 4.16, 0.5], - [false, 1.2, 4.16, 0.6912], - [false, 1.4, 4.16, 1.0976], - [false, 1.6, 5.632, 1.6384], - [false, 1.8, 5.632, 2.3328], - [true, 2, 4.3264, 3.02848], - [false, 2.2, 4.3264, 3.486639], - [false, 2.4, 4.3264, 3.821874], - [false, 2.6, 5.66528, 4.053386], - [false, 2.8, 5.66528, 4.200373], - [false, 3.0, 4.333056, 4.282037], - [true, 3.2, 4.333056, 2.997426], - [false, 3.4, 4.333056, 3.452226], - ]; - - testCases.forEach(([isThrottlingErrorReturn, timestamp, measuredTxRate, fillRate]) => { - (isThrottlingError as jest.Mock).mockReturnValue(isThrottlingErrorReturn); - jest.spyOn(Date, "now").mockImplementation(() => timestamp * 1000); - - rateLimiter.updateClientSendingRate({}); - expect(rateLimiter["measuredTxRate"]).toEqual(measuredTxRate); - expect(parseFloat(rateLimiter["fillRate"].toFixed(6))).toEqual(fillRate); - }); - }); -}); diff --git a/packages/util-retry/src/DefaultRateLimiter.ts b/packages/util-retry/src/DefaultRateLimiter.ts deleted file mode 100644 index 315c73139619..000000000000 --- a/packages/util-retry/src/DefaultRateLimiter.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { isThrottlingError } from "@aws-sdk/service-error-classification"; - -import { RateLimiter } from "./types"; - -/** - * @public - */ -export interface DefaultRateLimiterOptions { - beta?: number; - minCapacity?: number; - minFillRate?: number; - scaleConstant?: number; - smooth?: number; -} - -/** - * @public - */ -export class DefaultRateLimiter implements RateLimiter { - // User configurable constants - private beta: number; - private minCapacity: number; - private minFillRate: number; - private scaleConstant: number; - private smooth: number; - - // Pre-set state variables - private currentCapacity = 0; - private enabled = false; - private lastMaxRate = 0; - private measuredTxRate = 0; - private requestCount = 0; - - // Other state variables - private fillRate: number; - private lastThrottleTime: number; - private lastTimestamp = 0; - private lastTxRateBucket: number; - private maxCapacity: number; - private timeWindow = 0; - - constructor(options?: DefaultRateLimiterOptions) { - this.beta = options?.beta ?? 0.7; - this.minCapacity = options?.minCapacity ?? 1; - this.minFillRate = options?.minFillRate ?? 0.5; - this.scaleConstant = options?.scaleConstant ?? 0.4; - this.smooth = options?.smooth ?? 0.8; - - const currentTimeInSeconds = this.getCurrentTimeInSeconds(); - this.lastThrottleTime = currentTimeInSeconds; - this.lastTxRateBucket = Math.floor(this.getCurrentTimeInSeconds()); - - this.fillRate = this.minFillRate; - this.maxCapacity = this.minCapacity; - } - - private getCurrentTimeInSeconds() { - return Date.now() / 1000; - } - - public async getSendToken() { - return this.acquireTokenBucket(1); - } - - private async acquireTokenBucket(amount: number) { - // Client side throttling is not enabled until we see a throttling error. - if (!this.enabled) { - return; - } - - this.refillTokenBucket(); - if (amount > this.currentCapacity) { - const delay = ((amount - this.currentCapacity) / this.fillRate) * 1000; - await new Promise((resolve) => setTimeout(resolve, delay)); - } - this.currentCapacity = this.currentCapacity - amount; - } - - private refillTokenBucket() { - const timestamp = this.getCurrentTimeInSeconds(); - if (!this.lastTimestamp) { - this.lastTimestamp = timestamp; - return; - } - - const fillAmount = (timestamp - this.lastTimestamp) * this.fillRate; - this.currentCapacity = Math.min(this.maxCapacity, this.currentCapacity + fillAmount); - this.lastTimestamp = timestamp; - } - - public updateClientSendingRate(response: any) { - let calculatedRate: number; - this.updateMeasuredRate(); - - if (isThrottlingError(response)) { - const rateToUse = !this.enabled ? this.measuredTxRate : Math.min(this.measuredTxRate, this.fillRate); - this.lastMaxRate = rateToUse; - this.calculateTimeWindow(); - this.lastThrottleTime = this.getCurrentTimeInSeconds(); - calculatedRate = this.cubicThrottle(rateToUse); - this.enableTokenBucket(); - } else { - this.calculateTimeWindow(); - calculatedRate = this.cubicSuccess(this.getCurrentTimeInSeconds()); - } - - const newRate = Math.min(calculatedRate, 2 * this.measuredTxRate); - this.updateTokenBucketRate(newRate); - } - - private calculateTimeWindow() { - this.timeWindow = this.getPrecise(Math.pow((this.lastMaxRate * (1 - this.beta)) / this.scaleConstant, 1 / 3)); - } - - private cubicThrottle(rateToUse: number) { - return this.getPrecise(rateToUse * this.beta); - } - - private cubicSuccess(timestamp: number) { - return this.getPrecise( - this.scaleConstant * Math.pow(timestamp - this.lastThrottleTime - this.timeWindow, 3) + this.lastMaxRate - ); - } - - private enableTokenBucket() { - this.enabled = true; - } - - private updateTokenBucketRate(newRate: number) { - // Refill based on our current rate before we update to the new fill rate. - this.refillTokenBucket(); - - this.fillRate = Math.max(newRate, this.minFillRate); - this.maxCapacity = Math.max(newRate, this.minCapacity); - - // When we scale down we can't have a current capacity that exceeds our maxCapacity. - this.currentCapacity = Math.min(this.currentCapacity, this.maxCapacity); - } - - private updateMeasuredRate() { - const t = this.getCurrentTimeInSeconds(); - const timeBucket = Math.floor(t * 2) / 2; - this.requestCount++; - - if (timeBucket > this.lastTxRateBucket) { - const currentRate = this.requestCount / (timeBucket - this.lastTxRateBucket); - this.measuredTxRate = this.getPrecise(currentRate * this.smooth + this.measuredTxRate * (1 - this.smooth)); - this.requestCount = 0; - this.lastTxRateBucket = timeBucket; - } - } - - private getPrecise(num: number) { - return parseFloat(num.toFixed(8)); - } -} diff --git a/packages/util-retry/src/StandardRetryStrategy.spec.ts b/packages/util-retry/src/StandardRetryStrategy.spec.ts deleted file mode 100644 index 79d5f95f62a9..000000000000 --- a/packages/util-retry/src/StandardRetryStrategy.spec.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { RetryErrorInfo, RetryErrorType } from "@aws-sdk/types"; - -import { RETRY_MODES } from "./config"; -import { DEFAULT_RETRY_DELAY_BASE, INITIAL_RETRY_TOKENS } from "./constants"; -import { createDefaultRetryToken } from "./defaultRetryToken"; -import { StandardRetryStrategy } from "./StandardRetryStrategy"; - -jest.mock("./defaultRetryToken"); - -describe(StandardRetryStrategy.name, () => { - const maxAttempts = 3; - const retryTokenScope = "scope"; - const mockRetryToken = { - getRetryCount: () => 1, - getRetryTokenCount: (errorInfo: any) => 1, - }; - const noRetryTokenAvailableError = new Error("No retry token available"); - const errorInfo = { errorType: "TRANSIENT" } as RetryErrorInfo; - - beforeEach(() => { - (createDefaultRetryToken as jest.Mock).mockReturnValue(mockRetryToken); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("sets maxAttemptsProvider as a class member variable", () => { - [1, 2, 3].forEach((maxAttempts) => { - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - expect(retryStrategy["maxAttemptsProvider"]()).resolves.toBe(maxAttempts); - }); - }); - - it(`sets mode=${RETRY_MODES.STANDARD}`, () => { - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - expect(retryStrategy.mode).toStrictEqual(RETRY_MODES.STANDARD); - }); - - describe("acquireInitialRetryToken", () => { - it("returns default retryToken", async () => { - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - const retryToken = await retryStrategy.acquireInitialRetryToken(retryTokenScope); - expect(retryToken).toEqual( - createDefaultRetryToken({ - retryDelay: DEFAULT_RETRY_DELAY_BASE, - retryCount: 0, - }) - ); - }); - }); - - describe("refreshRetryTokenForRetry", () => { - it("refreshes the token", async () => { - const getRetryCount = jest.fn().mockReturnValue(0); - const hasRetryTokens = jest.fn().mockReturnValue(true); - const mockRetryToken = { - getRetryCount, - hasRetryTokens, - }; - (createDefaultRetryToken as jest.Mock).mockReturnValue(mockRetryToken); - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - const token = await retryStrategy.acquireInitialRetryToken(retryTokenScope); - await retryStrategy.refreshRetryTokenForRetry(token, errorInfo); - expect(getRetryCount).toHaveBeenCalledTimes(3); - }); - - it("disables any retries when maxAttempts is 1", async () => { - const mockRetryToken = { - getRetryCount: () => 0, - getRetryTokenCount: (errorInfo: any) => 1, - }; - (createDefaultRetryToken as jest.Mock).mockReturnValue(mockRetryToken); - const retryStrategy = new StandardRetryStrategy(1); - const token = await retryStrategy.acquireInitialRetryToken(retryTokenScope); - try { - await retryStrategy.refreshRetryTokenForRetry(token, errorInfo); - fail(`expected ${noRetryTokenAvailableError}`); - } catch (error) { - expect(error).toStrictEqual(noRetryTokenAvailableError); - } - }); - - it("throws when attempts exceeds maxAttempts", async () => { - const mockRetryToken = { - getRetryCount: () => 2, - getRetryTokenCount: (errorInfo: any) => 1, - }; - (createDefaultRetryToken as jest.Mock).mockReturnValue(mockRetryToken); - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(1)); - const token = await retryStrategy.acquireInitialRetryToken(retryTokenScope); - try { - await retryStrategy.refreshRetryTokenForRetry(token, errorInfo); - fail(`expected ${noRetryTokenAvailableError}`); - } catch (error) { - expect(error).toStrictEqual(noRetryTokenAvailableError); - } - }); - - it("throws when attempts exceeds default max attempts (3)", async () => { - const mockRetryToken = { - getRetryCount: () => 5, - getRetryTokenCount: (errorInfo: any) => 1, - }; - (createDefaultRetryToken as jest.Mock).mockReturnValue(mockRetryToken); - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(5)); - const token = await retryStrategy.acquireInitialRetryToken(retryTokenScope); - try { - await retryStrategy.refreshRetryTokenForRetry(token, errorInfo); - fail(`expected ${noRetryTokenAvailableError}`); - } catch (error) { - expect(error).toStrictEqual(noRetryTokenAvailableError); - } - }); - - it("throws when error is non-retryable", async () => { - const mockRetryToken = { - getRetryCount: () => 0, - getRetryTokenCount: (errorInfo: any) => 1, - hasRetryTokens: (errorType: RetryErrorType) => true, - }; - (createDefaultRetryToken as jest.Mock).mockReturnValue(mockRetryToken); - const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts)); - const token = await retryStrategy.acquireInitialRetryToken(retryTokenScope); - const errorInfo = { - errorType: "CLIENT_ERROR", - } as RetryErrorInfo; - try { - await retryStrategy.refreshRetryTokenForRetry(token, errorInfo); - fail(`expected ${noRetryTokenAvailableError}`); - } catch (error) { - expect(error).toStrictEqual(noRetryTokenAvailableError); - } - }); - }); -}); diff --git a/packages/util-retry/src/StandardRetryStrategy.ts b/packages/util-retry/src/StandardRetryStrategy.ts deleted file mode 100644 index c5c6093c5469..000000000000 --- a/packages/util-retry/src/StandardRetryStrategy.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Provider, RetryErrorInfo, RetryErrorType, RetryStrategyV2, StandardRetryToken } from "@aws-sdk/types"; - -import { DEFAULT_MAX_ATTEMPTS, RETRY_MODES } from "./config"; -import { - DEFAULT_RETRY_DELAY_BASE, - INITIAL_RETRY_TOKENS, - NO_RETRY_INCREMENT, - RETRY_COST, - THROTTLING_RETRY_DELAY_BASE, - TIMEOUT_RETRY_COST, -} from "./constants"; -import { getDefaultRetryBackoffStrategy } from "./defaultRetryBackoffStrategy"; -import { createDefaultRetryToken } from "./defaultRetryToken"; - -/** - * @public - */ -export class StandardRetryStrategy implements RetryStrategyV2 { - public readonly mode: string = RETRY_MODES.STANDARD; - private capacity: number = INITIAL_RETRY_TOKENS; - private readonly retryBackoffStrategy = getDefaultRetryBackoffStrategy(); - private readonly maxAttemptsProvider: Provider; - - constructor(maxAttempts: number); - constructor(maxAttemptsProvider: Provider); - constructor(private readonly maxAttempts: number | Provider) { - this.maxAttemptsProvider = typeof maxAttempts === "function" ? maxAttempts : async () => maxAttempts; - } - - public async acquireInitialRetryToken(retryTokenScope: string): Promise { - return createDefaultRetryToken({ - retryDelay: DEFAULT_RETRY_DELAY_BASE, - retryCount: 0, - }); - } - - public async refreshRetryTokenForRetry( - token: StandardRetryToken, - errorInfo: RetryErrorInfo - ): Promise { - const maxAttempts = await this.getMaxAttempts(); - - if (this.shouldRetry(token, errorInfo, maxAttempts)) { - const errorType = errorInfo.errorType; - this.retryBackoffStrategy.setDelayBase( - errorType === "THROTTLING" ? THROTTLING_RETRY_DELAY_BASE : DEFAULT_RETRY_DELAY_BASE - ); - - const delayFromErrorType = this.retryBackoffStrategy.computeNextBackoffDelay(token.getRetryCount()); - const retryDelay = errorInfo.retryAfterHint - ? Math.max(errorInfo.retryAfterHint.getTime() - Date.now() || 0, delayFromErrorType) - : delayFromErrorType; - - const capacityCost = this.getCapacityCost(errorType); - this.capacity -= capacityCost; - return createDefaultRetryToken({ - retryDelay, - retryCount: token.getRetryCount() + 1, - retryCost: capacityCost, - }); - } - - throw new Error("No retry token available"); - } - - public recordSuccess(token: StandardRetryToken): void { - this.capacity = Math.max(INITIAL_RETRY_TOKENS, this.capacity + (token.getRetryCost() ?? NO_RETRY_INCREMENT)); - } - - /** - * @returns the current available retry capacity. - * - * This number decreases when retries are executed and refills when requests or retries succeed. - */ - public getCapacity(): number { - return this.capacity; - } - - private async getMaxAttempts() { - try { - return await this.maxAttemptsProvider(); - } catch (error) { - console.warn(`Max attempts provider could not resolve. Using default of ${DEFAULT_MAX_ATTEMPTS}`); - return DEFAULT_MAX_ATTEMPTS; - } - } - - private shouldRetry(tokenToRenew: StandardRetryToken, errorInfo: RetryErrorInfo, maxAttempts: number): boolean { - const attempts = tokenToRenew.getRetryCount() + 1; - - return ( - attempts < maxAttempts && - this.capacity >= this.getCapacityCost(errorInfo.errorType) && - this.isRetryableError(errorInfo.errorType) - ); - } - - private getCapacityCost(errorType: RetryErrorType) { - return errorType === "TRANSIENT" ? TIMEOUT_RETRY_COST : RETRY_COST; - } - - private isRetryableError(errorType: RetryErrorType): boolean { - return errorType === "THROTTLING" || errorType === "TRANSIENT"; - } -} diff --git a/packages/util-retry/src/config.ts b/packages/util-retry/src/config.ts deleted file mode 100644 index 9d3034d19549..000000000000 --- a/packages/util-retry/src/config.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @public - */ -export enum RETRY_MODES { - STANDARD = "standard", - ADAPTIVE = "adaptive", -} - -/** - * @public - * - * The default value for how many HTTP requests an SDK should make for a - * single SDK operation invocation before giving up - */ -export const DEFAULT_MAX_ATTEMPTS = 3; - -/** - * @public - * - * The default retry algorithm to use. - */ -export const DEFAULT_RETRY_MODE = RETRY_MODES.STANDARD; diff --git a/packages/util-retry/src/constants.ts b/packages/util-retry/src/constants.ts deleted file mode 100644 index 7ca7a8203265..000000000000 --- a/packages/util-retry/src/constants.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @public - * - * The base number of milliseconds to use in calculating a suitable cool-down - * time when a retryable error is encountered. - */ -export const DEFAULT_RETRY_DELAY_BASE = 100; - -/** - * @public - * - * The maximum amount of time (in milliseconds) that will be used as a delay - * between retry attempts. - */ -export const MAXIMUM_RETRY_DELAY = 20 * 1000; - -/** - * @public - * - * The retry delay base (in milliseconds) to use when a throttling error is - * encountered. - */ -export const THROTTLING_RETRY_DELAY_BASE = 500; - -/** - * @public - * - * Initial number of retry tokens in Retry Quota - */ -export const INITIAL_RETRY_TOKENS = 500; - -/** - * @public - * - * The total amount of retry tokens to be decremented from retry token balance. - */ -export const RETRY_COST = 5; - -/** - * @public - * - * The total amount of retry tokens to be decremented from retry token balance - * when a throttling error is encountered. - */ -export const TIMEOUT_RETRY_COST = 10; - -/** - * @public - * - * The total amount of retry token to be incremented from retry token balance - * if an SDK operation invocation succeeds without requiring a retry request. - */ -export const NO_RETRY_INCREMENT = 1; - -/** - * @public - * - * Header name for SDK invocation ID - */ -export const INVOCATION_ID_HEADER = "amz-sdk-invocation-id"; - -/** - * @public - * - * Header name for request retry information. - */ -export const REQUEST_HEADER = "amz-sdk-request"; diff --git a/packages/util-retry/src/defaultRetryBackoffStrategy.spec.ts b/packages/util-retry/src/defaultRetryBackoffStrategy.spec.ts deleted file mode 100644 index dc0af34aed98..000000000000 --- a/packages/util-retry/src/defaultRetryBackoffStrategy.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { DEFAULT_RETRY_DELAY_BASE, MAXIMUM_RETRY_DELAY } from "./constants"; -import { getDefaultRetryBackoffStrategy } from "./defaultRetryBackoffStrategy"; - -describe("defaultRetryBackoffStrategy", () => { - const mathDotRandom = Math.random; - - beforeEach(() => { - Math.random = jest.fn().mockReturnValue(1); - }); - - afterEach(() => { - Math.random = mathDotRandom; - }); - - describe(`uses ${DEFAULT_RETRY_DELAY_BASE} by default`, () => { - [0, 1, 2, 3].forEach((attempts) => { - const expectedDelay = Math.floor(2 ** attempts * DEFAULT_RETRY_DELAY_BASE); - const retryBackoffStrategy = getDefaultRetryBackoffStrategy(); - it(`(${attempts}) returns ${expectedDelay}`, () => { - expect(retryBackoffStrategy.computeNextBackoffDelay(attempts)).toBe(expectedDelay); - }); - }); - }); - - describe("retry delay increases exponentially with attempt number", () => { - [0, 1, 2, 3].forEach((attempts) => { - const mockDelayBase = 50; - const expectedDelay = Math.floor(2 ** attempts * mockDelayBase); - const retryBackoffStrategy = getDefaultRetryBackoffStrategy(); - retryBackoffStrategy.setDelayBase(mockDelayBase); - it(`(${attempts}) returns ${expectedDelay}`, () => { - expect(retryBackoffStrategy.computeNextBackoffDelay(attempts)).toBe(expectedDelay); - }); - }); - }); - - describe(`caps retry delay at ${MAXIMUM_RETRY_DELAY / 1000} seconds`, () => { - const retryBackoffStrategy = getDefaultRetryBackoffStrategy(); - it("when value exceeded because of high delayBase", () => { - retryBackoffStrategy.setDelayBase(MAXIMUM_RETRY_DELAY + 1); - expect(retryBackoffStrategy.computeNextBackoffDelay(0)).toBe(MAXIMUM_RETRY_DELAY); - retryBackoffStrategy.setDelayBase(MAXIMUM_RETRY_DELAY + 2); - expect(retryBackoffStrategy.computeNextBackoffDelay(0)).toBe(MAXIMUM_RETRY_DELAY); - }); - - it("when value exceeded because of high attempts number", () => { - const largeAttemptsNumber = Math.ceil(Math.log2(MAXIMUM_RETRY_DELAY)); - retryBackoffStrategy.setDelayBase(1); - expect(retryBackoffStrategy.computeNextBackoffDelay(largeAttemptsNumber)).toBe(MAXIMUM_RETRY_DELAY); - expect(retryBackoffStrategy.computeNextBackoffDelay(largeAttemptsNumber + 1)).toBe(MAXIMUM_RETRY_DELAY); - }); - }); - - describe("randomizes the retry delay value", () => { - const retryBackoffStrategy = getDefaultRetryBackoffStrategy(); - Array.from({ length: 3 }, () => Math.random()).forEach((mockRandomValue) => { - const attempts = 0; - const delayBase = 100; - const expectedDelay = Math.floor(mockRandomValue * 2 ** attempts * delayBase); - retryBackoffStrategy.setDelayBase(delayBase); - it(`(${delayBase}, ${attempts}) with mock Math.random=${mockRandomValue} returns ${expectedDelay}`, () => { - Math.random = jest.fn().mockReturnValue(mockRandomValue); - expect(retryBackoffStrategy.computeNextBackoffDelay(attempts)).toBe(expectedDelay); - }); - }); - }); -}); diff --git a/packages/util-retry/src/defaultRetryBackoffStrategy.ts b/packages/util-retry/src/defaultRetryBackoffStrategy.ts deleted file mode 100644 index 4d32db740fa6..000000000000 --- a/packages/util-retry/src/defaultRetryBackoffStrategy.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { StandardRetryBackoffStrategy } from "@aws-sdk/types"; - -import { DEFAULT_RETRY_DELAY_BASE, MAXIMUM_RETRY_DELAY } from "./constants"; - -/** - * @internal - */ -export const getDefaultRetryBackoffStrategy = (): StandardRetryBackoffStrategy => { - let delayBase: number = DEFAULT_RETRY_DELAY_BASE; - - const computeNextBackoffDelay = (attempts: number) => { - return Math.floor(Math.min(MAXIMUM_RETRY_DELAY, Math.random() * 2 ** attempts * delayBase)); - }; - - const setDelayBase = (delay: number) => { - delayBase = delay; - }; - - return { - computeNextBackoffDelay, - setDelayBase, - }; -}; diff --git a/packages/util-retry/src/defaultRetryToken.spec.ts b/packages/util-retry/src/defaultRetryToken.spec.ts deleted file mode 100644 index e0604d4a79c1..000000000000 --- a/packages/util-retry/src/defaultRetryToken.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { DEFAULT_RETRY_DELAY_BASE, MAXIMUM_RETRY_DELAY } from "./constants"; -import { createDefaultRetryToken } from "./defaultRetryToken"; - -jest.mock("./defaultRetryBackoffStrategy"); - -describe("defaultRetryToken", () => { - describe("getRetryCost", () => { - it("is undefined before an error is encountered", () => { - const retryToken = createDefaultRetryToken({ - retryDelay: DEFAULT_RETRY_DELAY_BASE, - retryCount: 0, - }); - expect(retryToken.getRetryCost()).toBeUndefined(); - }); - - it("returns set value", () => { - const retryToken = createDefaultRetryToken({ - retryDelay: DEFAULT_RETRY_DELAY_BASE, - retryCount: 0, - retryCost: 25, - }); - expect(retryToken.getRetryCost()).toBe(25); - }); - }); - - describe("getRetryCount", () => { - it("returns amount set when token is created", () => { - const retryCount = 3; - const retryToken = createDefaultRetryToken({ - retryDelay: DEFAULT_RETRY_DELAY_BASE, - retryCount, - }); - expect(retryToken.getRetryCount()).toBe(retryCount); - }); - }); - - describe("getRetryDelay", () => { - it("returns initial delay", () => { - const retryToken = createDefaultRetryToken({ - retryDelay: DEFAULT_RETRY_DELAY_BASE, - retryCount: 0, - }); - expect(retryToken.getRetryDelay()).toBe(DEFAULT_RETRY_DELAY_BASE); - }); - - describe(`caps retry delay at ${MAXIMUM_RETRY_DELAY / 1000} seconds`, () => { - it("when value exceeded because of high delayBase", () => { - const retryToken = createDefaultRetryToken({ - retryDelay: DEFAULT_RETRY_DELAY_BASE * 1000, - retryCount: 0, - }); - expect(retryToken.getRetryDelay()).toBe(MAXIMUM_RETRY_DELAY); - }); - }); - }); -}); diff --git a/packages/util-retry/src/defaultRetryToken.ts b/packages/util-retry/src/defaultRetryToken.ts deleted file mode 100644 index e5ab316ae2d4..000000000000 --- a/packages/util-retry/src/defaultRetryToken.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { StandardRetryToken } from "@aws-sdk/types"; - -import { MAXIMUM_RETRY_DELAY } from "./constants"; - -/** - * @internal - */ -export const createDefaultRetryToken = ({ - retryDelay, - retryCount, - retryCost, -}: { - retryDelay: number; - retryCount: number; - retryCost?: number; -}): StandardRetryToken => { - const getRetryCount = (): number => retryCount; - const getRetryDelay = (): number => Math.min(MAXIMUM_RETRY_DELAY, retryDelay); - const getRetryCost = (): number | undefined => retryCost; - - return { - getRetryCount, - getRetryDelay, - getRetryCost, - }; -}; diff --git a/packages/util-retry/src/index.ts b/packages/util-retry/src/index.ts index 8637ced053e7..03026ec49c55 100644 --- a/packages/util-retry/src/index.ts +++ b/packages/util-retry/src/index.ts @@ -1,7 +1 @@ -export * from "./AdaptiveRetryStrategy"; -export * from "./ConfiguredRetryStrategy"; -export * from "./DefaultRateLimiter"; -export * from "./StandardRetryStrategy"; -export * from "./config"; -export * from "./constants"; -export * from "./types"; +export * from "@smithy/util-retry"; diff --git a/packages/util-retry/src/types.ts b/packages/util-retry/src/types.ts deleted file mode 100644 index f1001e8ddd39..000000000000 --- a/packages/util-retry/src/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @internal - */ -export interface RateLimiter { - /** - * If there is sufficient capacity (tokens) available, it immediately returns. - * If there is not sufficient capacity, it will either sleep a certain amount - * of time until the rate limiter can retrieve a token from its token bucket - * or raise an exception indicating there is insufficient capacity. - */ - getSendToken: () => Promise; - - /** - * Updates the client sending rate based on response. - * If the response was successful, the capacity and fill rate are increased. - * If the response was a throttling response, the capacity and fill rate are - * decreased. Transient errors do not affect the rate limiter. - */ - updateClientSendingRate: (response: any) => void; -} diff --git a/packages/util-stream-browser/jest.config.js b/packages/util-stream-browser/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/util-stream-browser/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/util-stream-browser/package.json b/packages/util-stream-browser/package.json index 66e6047ad63e..b74488e0b5f0 100644 --- a/packages/util-stream-browser/package.json +++ b/packages/util-stream-browser/package.json @@ -8,7 +8,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-es/index.js", "module": "./dist-es/index.js", @@ -19,11 +19,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/fetch-http-handler": "*", - "@aws-sdk/types": "*", - "@aws-sdk/util-base64": "*", - "@aws-sdk/util-hex-encoding": "*", - "@aws-sdk/util-utf8": "*", + "@smithy/util-stream-browser": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/util-stream-browser/src/getAwsChunkedEncodingStream.ts b/packages/util-stream-browser/src/getAwsChunkedEncodingStream.ts deleted file mode 100644 index 12c47b70b612..000000000000 --- a/packages/util-stream-browser/src/getAwsChunkedEncodingStream.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { GetAwsChunkedEncodingStream, GetAwsChunkedEncodingStreamOptions } from "@aws-sdk/types"; - -/** - * @internal - */ -export const getAwsChunkedEncodingStream: GetAwsChunkedEncodingStream = ( - readableStream: ReadableStream, - options: GetAwsChunkedEncodingStreamOptions -) => { - const { base64Encoder, bodyLengthChecker, checksumAlgorithmFn, checksumLocationName, streamHasher } = options; - - const checksumRequired = - base64Encoder !== undefined && - bodyLengthChecker !== undefined && - checksumAlgorithmFn !== undefined && - checksumLocationName !== undefined && - streamHasher !== undefined; - const digest = checksumRequired ? streamHasher!(checksumAlgorithmFn!, readableStream) : undefined; - - // ToDo: Validate the ReadableStream and getReader() is accessable before calling. - // ReactNative doesn't support ReadableStream. They might not be available in older browsers, or some polyfills. - const reader = readableStream.getReader(); - return new ReadableStream({ - async pull(controller) { - const { value, done } = await reader.read(); - - if (done) { - controller.enqueue(`0\r\n`); - if (checksumRequired) { - const checksum = base64Encoder!(await digest!); - controller.enqueue(`${checksumLocationName}:${checksum}\r\n`); - controller.enqueue(`\r\n`); - } - controller.close(); - } else { - controller.enqueue(`${(bodyLengthChecker(value) || 0).toString(16)}\r\n${value}\r\n`); - } - }, - }); -}; diff --git a/packages/util-stream-browser/src/index.ts b/packages/util-stream-browser/src/index.ts index b20a1854a9d6..503b7af86de3 100644 --- a/packages/util-stream-browser/src/index.ts +++ b/packages/util-stream-browser/src/index.ts @@ -1,8 +1 @@ -/** - * @internal - */ -export * from "./getAwsChunkedEncodingStream"; -/** - * @internal - */ -export * from "./sdk-stream-mixin"; +export * from "@smithy/util-stream-browser"; diff --git a/packages/util-stream-browser/src/sdk-stream-mixin.spec.ts b/packages/util-stream-browser/src/sdk-stream-mixin.spec.ts deleted file mode 100644 index 6a55fc2c4dba..000000000000 --- a/packages/util-stream-browser/src/sdk-stream-mixin.spec.ts +++ /dev/null @@ -1,222 +0,0 @@ -// @jest-environment jsdom -import { streamCollector } from "@aws-sdk/fetch-http-handler"; -import { SdkStreamMixin } from "@aws-sdk/types"; -import { toBase64 } from "@aws-sdk/util-base64"; -import { toHex } from "@aws-sdk/util-hex-encoding"; -import { toUtf8 } from "@aws-sdk/util-utf8"; - -import { sdkStreamMixin } from "./sdk-stream-mixin"; - -jest.mock("@aws-sdk/fetch-http-handler"); -jest.mock("@aws-sdk/util-base64"); -jest.mock("@aws-sdk/util-hex-encoding"); -jest.mock("@aws-sdk/util-utf8"); - -const mockStreamCollectorReturn = Uint8Array.from([117, 112, 113]); -(streamCollector as jest.Mock).mockReturnValue(mockStreamCollectorReturn); - -describe(sdkStreamMixin.name, () => { - const expectAllTransformsToFail = async (sdkStream: SdkStreamMixin) => { - const transformMethods: Array = [ - "transformToByteArray", - "transformToString", - "transformToWebStream", - ]; - for (const method of transformMethods) { - try { - await sdkStream[method](); - fail(new Error("expect subsequent tranform to fail")); - } catch (error) { - expect(error.message).toContain("The stream has already been transformed"); - } - } - }; - - let originalReadableStreamCtr = global.ReadableStream; - const mockReadableStream = jest.fn(); - class ReadableStream { - constructor() { - mockReadableStream(); - } - } - - let payloadStream: ReadableStream; - - beforeAll(() => { - global.ReadableStream = ReadableStream as any; - }); - - beforeEach(() => { - originalReadableStreamCtr = global.ReadableStream; - jest.clearAllMocks(); - payloadStream = new ReadableStream(); - }); - - afterEach(() => { - global.ReadableStream = originalReadableStreamCtr; - }); - - it("should throw if input stream is not a Blob or Web Stream instance", () => { - const originalBlobCtr = global.Blob; - global.Blob = undefined; - global.ReadableStream = undefined; - try { - sdkStreamMixin({}); - fail("expect unexpected stream to fail"); - } catch (e) { - expect(e.message).toContain("nexpected stream implementation"); - global.Blob = originalBlobCtr; - } - }); - - describe("transformToByteArray", () => { - it("should transform binary stream to byte array", async () => { - const sdkStream = sdkStreamMixin(payloadStream); - const byteArray = await sdkStream.transformToByteArray(); - expect(streamCollector as jest.Mock).toBeCalledWith(payloadStream); - expect(byteArray).toEqual(mockStreamCollectorReturn); - }); - - it("should fail any subsequent tranform calls", async () => { - const sdkStream = sdkStreamMixin(payloadStream); - await sdkStream.transformToByteArray(); - await expectAllTransformsToFail(sdkStream); - }); - }); - - describe("transformToString", () => { - let originalTextDecoder = global.TextDecoder; - const mockDecode = jest.fn(); - global.TextDecoder = jest.fn().mockImplementation(function () { - return { decode: mockDecode }; - }); - - beforeEach(() => { - originalTextDecoder = global.TextDecoder; - jest.clearAllMocks(); - }); - - afterEach(() => { - global.TextDecoder = originalTextDecoder; - }); - - it.each([ - [undefined, toUtf8], - ["utf8", toUtf8], - ["utf-8", toUtf8], - ["base64", toBase64], - ["hex", toHex], - ])("should transform to string with %s encoding", async (encoding, encodingFn) => { - const mockEncodedStringValue = `a string with ${encoding} encoding`; - (encodingFn as jest.Mock).mockReturnValueOnce(mockEncodedStringValue); - const sdkStream = sdkStreamMixin(payloadStream); - const str = await sdkStream.transformToString(encoding); - expect(streamCollector).toBeCalled(); - expect(encodingFn).toBeCalledWith(mockStreamCollectorReturn); - expect(str).toEqual(mockEncodedStringValue); - }); - - it("should use TexDecoder to handle other encodings", async () => { - const utfLabel = "windows-1251"; - mockDecode.mockReturnValue(`a string with ${utfLabel} encoding`); - const sdkStream = sdkStreamMixin(payloadStream); - const str = await sdkStream.transformToString(utfLabel); - expect(global.TextDecoder).toBeCalledWith(utfLabel); - expect(str).toEqual(`a string with ${utfLabel} encoding`); - }); - - it("should throw if TextDecoder is not available", async () => { - global.TextDecoder = null; - const utfLabel = "windows-1251"; - const sdkStream = sdkStreamMixin(payloadStream); - try { - await sdkStream.transformToString(utfLabel); - fail("expect transformToString to throw when TextDecoder is not available"); - } catch (error) { - expect(error.message).toContain("TextDecoder is not available"); - } - }); - - it("should fail any subsequent tranform calls", async () => { - const sdkStream = sdkStreamMixin(payloadStream); - await sdkStream.transformToString(); - await expectAllTransformsToFail(sdkStream); - }); - }); - - describe("transformToWebStream with ReadableStream payload", () => { - it("should return the payload if it is Web Stream instance", () => { - const payloadStream = new ReadableStream(); - const sdkStream = sdkStreamMixin(payloadStream as any); - const transformed = sdkStream.transformToWebStream(); - expect(transformed).toBe(payloadStream); - }); - - it("should fail any subsequent tranform calls", async () => { - const payloadStream = new ReadableStream(); - const sdkStream = sdkStreamMixin(payloadStream as any); - sdkStream.transformToWebStream(); - await expectAllTransformsToFail(sdkStream); - }); - }); - - describe("transformToWebStream with Blob payload", () => { - let originalBlobCtr = global.Blob; - const mockBlob = jest.fn(); - const mockBlobStream = jest.fn(); - class Blob { - constructor() { - mockBlob(); - } - - stream() { - return mockBlobStream(); - } - } - global.Blob = Blob as any; - - beforeEach(() => { - global.ReadableStream = undefined; - originalBlobCtr = global.Blob; - jest.clearAllMocks(); - }); - - afterEach(() => { - global.Blob = originalBlobCtr; - }); - - it("should transform blob to web stream with Blob.stream()", () => { - mockBlobStream.mockReturnValue("transformed"); - const payloadStream = new Blob(); - const sdkStream = sdkStreamMixin(payloadStream as any); - const transformed = sdkStream.transformToWebStream(); - expect(transformed).toBe("transformed"); - expect(mockBlobStream).toBeCalled(); - }); - - it("should fail if Blob.stream() is not available", async () => { - class Blob { - constructor() { - mockBlob(); - } - } - - global.Blob = Blob as any; - const payloadStream = new Blob(); - const sdkStream = sdkStreamMixin(payloadStream as any); - try { - sdkStream.transformToWebStream(); - fail("expect to fail"); - } catch (e) { - expect(e.message).toContain("Please make sure the Blob.stream() is polyfilled"); - } - }); - - it("should fail any subsequent tranform calls", async () => { - const payloadStream = new Blob(); - const sdkStream = sdkStreamMixin(payloadStream as any); - sdkStream.transformToWebStream(); - await expectAllTransformsToFail(sdkStream); - }); - }); -}); diff --git a/packages/util-stream-browser/src/sdk-stream-mixin.ts b/packages/util-stream-browser/src/sdk-stream-mixin.ts deleted file mode 100644 index d5bdebd93d12..000000000000 --- a/packages/util-stream-browser/src/sdk-stream-mixin.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { streamCollector } from "@aws-sdk/fetch-http-handler"; -import { SdkStream, SdkStreamMixin } from "@aws-sdk/types"; -import { toBase64 } from "@aws-sdk/util-base64"; -import { toHex } from "@aws-sdk/util-hex-encoding"; -import { toUtf8 } from "@aws-sdk/util-utf8"; - -const ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED = "The stream has already been transformed."; - -/** - * The stream handling utility functions for browsers and React Native - * - * @internal - */ -export const sdkStreamMixin = (stream: unknown): SdkStream => { - if (!isBlobInstance(stream) && !isReadableStreamInstance(stream)) { - //@ts-ignore - const name = stream?.__proto__?.constructor?.name || stream; - throw new Error(`Unexpected stream implementation, expect Blob or ReadableStream, got ${name}`); - } - - let transformed = false; - const transformToByteArray = async () => { - if (transformed) { - throw new Error(ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED); - } - transformed = true; - return await streamCollector(stream); - }; - - const blobToWebStream = (blob: Blob) => { - if (typeof blob.stream !== "function") { - throw new Error( - "Cannot transform payload Blob to web stream. Please make sure the Blob.stream() is polyfilled.\n" + - "If you are using React Native, this API is not yet supported, see: https://react-native.canny.io/feature-requests/p/fetch-streaming-body" - ); - } - return blob.stream(); - }; - - return Object.assign(stream, { - transformToByteArray: transformToByteArray, - - transformToString: async (encoding?: string) => { - const buf = await transformToByteArray(); - if (encoding === "base64") { - return toBase64(buf); - } else if (encoding === "hex") { - return toHex(buf); - } else if (encoding === undefined || encoding === "utf8" || encoding === "utf-8") { - // toUtf8() itself will use TextDecoder and fallback to pure JS implementation. - return toUtf8(buf); - } else if (typeof TextDecoder === "function") { - return new TextDecoder(encoding).decode(buf); - } else { - throw new Error("TextDecoder is not available, please make sure polyfill is provided."); - } - }, - - transformToWebStream: () => { - if (transformed) { - throw new Error(ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED); - } - transformed = true; - if (isBlobInstance(stream)) { - // ReadableStream is undefined in React Native - return blobToWebStream(stream); - } else if (isReadableStreamInstance(stream)) { - return stream; - } else { - throw new Error(`Cannot transform payload to web stream, got ${stream}`); - } - }, - }); -}; - -const isBlobInstance = (stream: unknown): stream is Blob => 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-node/jest.config.js b/packages/util-stream-node/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/util-stream-node/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/util-stream-node/package.json b/packages/util-stream-node/package.json index 8a52ad3cbe9d..e395d51a6284 100644 --- a/packages/util-stream-node/package.json +++ b/packages/util-stream-node/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,9 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/node-http-handler": "*", - "@aws-sdk/types": "*", - "@aws-sdk/util-buffer-from": "*", + "@smithy/util-stream-node": "^1.0.2", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/util-stream-node/src/getAwsChunkedEncodingStream.spec.ts b/packages/util-stream-node/src/getAwsChunkedEncodingStream.spec.ts deleted file mode 100644 index d593a5db7422..000000000000 --- a/packages/util-stream-node/src/getAwsChunkedEncodingStream.spec.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Readable } from "stream"; - -import { getAwsChunkedEncodingStream } from "./getAwsChunkedEncodingStream"; - -describe(getAwsChunkedEncodingStream.name, () => { - const mockBase64Encoder = jest.fn(); - const mockBodyLengthChecker = jest.fn(); - const mockChecksumAlgorithmFn = jest.fn(); - const mockChecksumLocationName = "mockChecksumLocationName"; - const mockStreamHasher = jest.fn(); - - const mockOptions = { - base64Encoder: mockBase64Encoder, - bodyLengthChecker: mockBodyLengthChecker, - checksumAlgorithmFn: mockChecksumAlgorithmFn, - checksumLocationName: mockChecksumLocationName, - streamHasher: mockStreamHasher, - }; - - const mockChecksum = "mockChecksum"; - const mockRawChecksum = Buffer.from(mockChecksum); - const mockStreamChunks = ["Hello", "World"]; - const mockBodyLength = 5; - - const getMockReadableStream = () => { - const readableStream = new Readable(); - mockStreamChunks.forEach((chunk) => { - readableStream.push(chunk); - }); - readableStream.push(null); - return readableStream; - }; - - beforeEach(() => { - mockStreamHasher.mockResolvedValue(mockRawChecksum); - mockBase64Encoder.mockReturnValue(mockChecksum); - mockBodyLengthChecker.mockReturnValue(mockBodyLength); - }); - - afterEach(() => { - expect(mockBodyLengthChecker).toHaveBeenCalledTimes(mockStreamChunks.length); - mockStreamChunks.forEach((chunk, index) => { - expect(mockBodyLengthChecker).toHaveBeenNthCalledWith(index + 1, Buffer.from(chunk)); - }); - jest.clearAllMocks(); - }); - - describe("skips checksum computation", () => { - const validateStreamWithoutChecksum = (awsChunkedEncodingStream: Readable, done: Function) => { - let buffer = ""; - awsChunkedEncodingStream.on("data", (data) => { - buffer += data.toString(); - }); - awsChunkedEncodingStream.on("end", () => { - expect(mockStreamHasher).not.toHaveBeenCalled(); - expect(mockBase64Encoder).not.toHaveBeenCalled(); - expect(buffer).toEqual(`5\r -Hello\r -5\r -World\r -0\r -`); - done(); - }); - }; - - it("if none of the required options are passed", (done) => { - const readableStream = getMockReadableStream(); - const awsChunkedEncodingStream = getAwsChunkedEncodingStream(readableStream, { - bodyLengthChecker: mockBodyLengthChecker, - }); - validateStreamWithoutChecksum(awsChunkedEncodingStream, done); - }); - - ["base64Encoder", "checksumAlgorithmFn", "checksumLocationName", "streamHasher"].forEach((optionToRemove) => { - it(`if option '${optionToRemove}' is not passed`, (done) => { - const readableStream = getMockReadableStream(); - const awsChunkedEncodingStream = getAwsChunkedEncodingStream(readableStream, { - ...mockOptions, - [optionToRemove]: undefined, - }); - validateStreamWithoutChecksum(awsChunkedEncodingStream, done); - }); - }); - }); - - it("computes checksum and adds it to the end event", (done) => { - const readableStream = getMockReadableStream(); - const awsChunkedEncodingStream = getAwsChunkedEncodingStream(readableStream, mockOptions); - - let buffer = ""; - awsChunkedEncodingStream.on("data", (data) => { - buffer += data.toString(); - }); - awsChunkedEncodingStream.on("end", () => { - expect(mockStreamHasher).toHaveBeenCalledWith(mockChecksumAlgorithmFn, readableStream); - expect(mockBase64Encoder).toHaveBeenCalledWith(mockRawChecksum); - expect(buffer).toStrictEqual(`5\r -Hello\r -5\r -World\r -0\r -mockChecksumLocationName:mockChecksum\r -\r -`); - done(); - }); - }); -}); diff --git a/packages/util-stream-node/src/getAwsChunkedEncodingStream.ts b/packages/util-stream-node/src/getAwsChunkedEncodingStream.ts deleted file mode 100644 index 27595267564c..000000000000 --- a/packages/util-stream-node/src/getAwsChunkedEncodingStream.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { GetAwsChunkedEncodingStream, GetAwsChunkedEncodingStreamOptions } from "@aws-sdk/types"; -import { Readable } from "stream"; - -/** - * @internal - */ -export const getAwsChunkedEncodingStream: GetAwsChunkedEncodingStream = ( - readableStream: Readable, - options: GetAwsChunkedEncodingStreamOptions -) => { - const { base64Encoder, bodyLengthChecker, checksumAlgorithmFn, checksumLocationName, streamHasher } = options; - - const checksumRequired = - base64Encoder !== undefined && - checksumAlgorithmFn !== undefined && - checksumLocationName !== undefined && - streamHasher !== undefined; - const digest = checksumRequired ? streamHasher!(checksumAlgorithmFn!, readableStream) : undefined; - - const awsChunkedEncodingStream = new Readable({ read: () => {} }); - readableStream.on("data", (data) => { - const length = bodyLengthChecker(data) || 0; - awsChunkedEncodingStream.push(`${length.toString(16)}\r\n`); - awsChunkedEncodingStream.push(data); - awsChunkedEncodingStream.push("\r\n"); - }); - readableStream.on("end", async () => { - awsChunkedEncodingStream.push(`0\r\n`); - if (checksumRequired) { - const checksum = base64Encoder!(await digest!); - awsChunkedEncodingStream.push(`${checksumLocationName}:${checksum}\r\n`); - awsChunkedEncodingStream.push(`\r\n`); - } - awsChunkedEncodingStream.push(null); - }); - return awsChunkedEncodingStream; -}; diff --git a/packages/util-stream-node/src/index.ts b/packages/util-stream-node/src/index.ts index b20a1854a9d6..86b8168f353b 100644 --- a/packages/util-stream-node/src/index.ts +++ b/packages/util-stream-node/src/index.ts @@ -1,8 +1 @@ -/** - * @internal - */ -export * from "./getAwsChunkedEncodingStream"; -/** - * @internal - */ -export * from "./sdk-stream-mixin"; +export * from "@smithy/util-stream-node"; diff --git a/packages/util-stream-node/src/sdk-stream-mixin.spec.ts b/packages/util-stream-node/src/sdk-stream-mixin.spec.ts deleted file mode 100644 index c3ee5128583b..000000000000 --- a/packages/util-stream-node/src/sdk-stream-mixin.spec.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { SdkStreamMixin } from "@aws-sdk/types"; -import { fromArrayBuffer } from "@aws-sdk/util-buffer-from"; -import { PassThrough, Readable, Writable } from "stream"; -import util from "util"; - -import { sdkStreamMixin } from "./sdk-stream-mixin"; - -jest.mock("@aws-sdk/util-buffer-from"); - -describe(sdkStreamMixin.name, () => { - const writeDataToStream = (stream: Writable, data: Array): Promise => - new Promise((resolve, reject) => { - data.forEach((chunk) => { - stream.write(chunk, (err) => { - if (err) reject(err); - }); - }); - stream.end(resolve); - }); - const byteArrayFromBuffer = (buf: Buffer) => new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); - let passThrough: PassThrough; - const expectAllTransformsToFail = async (sdkStream: SdkStreamMixin) => { - const transformMethods: Array = [ - "transformToByteArray", - "transformToString", - "transformToWebStream", - ]; - for (const method of transformMethods) { - try { - await sdkStream[method](); - fail(new Error("expect subsequent tranform to fail")); - } catch (error) { - expect(error.message).toContain("The stream has already been transformed"); - } - } - }; - - beforeEach(() => { - passThrough = new PassThrough(); - }); - - it("should throw if unexpected stream implementation is supplied", () => { - try { - const payload = {}; - sdkStreamMixin(payload); - fail("should throw when unexpected stream is supplied"); - } catch (error) { - expect(error.message).toContain("Unexpected stream implementation"); - } - }); - - describe("transformToByteArray", () => { - it("should transform binary stream to byte array", async () => { - const mockData = [Buffer.from("foo"), Buffer.from("bar"), Buffer.from("buzz")]; - const expected = byteArrayFromBuffer(Buffer.from("foobarbuzz")); - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, mockData); - expect(await sdkStream.transformToByteArray()).toEqual(expected); - }); - - it("should fail any subsequent tranform calls", async () => { - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, [Buffer.from("abc")]); - expect(await sdkStream.transformToByteArray()).toEqual(byteArrayFromBuffer(Buffer.from("abc"))); - await expectAllTransformsToFail(sdkStream); - }); - }); - - describe("transformToString", () => { - const toStringMock = jest.fn(); - beforeAll(() => { - jest.resetAllMocks(); - }); - - it("should transform the stream to string with utf-8 encoding by default", async () => { - (fromArrayBuffer as jest.Mock).mockImplementation( - jest.requireActual("@aws-sdk/util-buffer-from").fromArrayBuffer - ); - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, [Buffer.from("foo")]); - const transformed = await sdkStream.transformToString(); - expect(transformed).toEqual("foo"); - }); - - it.each([undefined, "utf-8", "ascii", "base64", "latin1", "binary"])( - "should transform the stream to string with %s encoding", - async (encoding) => { - (fromArrayBuffer as jest.Mock).mockReturnValue({ toString: toStringMock }); - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, [Buffer.from("foo")]); - await sdkStream.transformToString(encoding); - expect(toStringMock).toBeCalledWith(encoding); - } - ); - - it.each(["ibm866", "iso-8859-2", "koi8-r", "macintosh", "windows-874", "gbk", "gb18030", "euc-jp"])( - "should transform the stream to string with TextDecoder config %s", - async (encoding) => { - jest.spyOn(util, "TextDecoder").mockImplementation( - () => - ({ - decode: jest.fn(), - } as any) - ); - (fromArrayBuffer as jest.Mock).mockReturnValue({ toString: toStringMock }); - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, [Buffer.from("foo")]); - await sdkStream.transformToString(encoding as BufferEncoding); - expect(util.TextDecoder).toBeCalledWith(encoding); - } - ); - - it("should fail any subsequent tranform calls", async () => { - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, [Buffer.from("foo")]); - await sdkStream.transformToString(); - await expectAllTransformsToFail(sdkStream); - }); - }); - - describe("transformToWebStream", () => { - it("should throw if any event listener is attached on the underlying stream", async () => { - passThrough.on("data", console.log); - const sdkStream = sdkStreamMixin(passThrough); - try { - sdkStream.transformToWebStream(); - fail(new Error("expect web stream transformation to fail")); - } catch (error) { - expect(error.message).toContain("The stream has been consumed by other callbacks"); - } - }); - - describe("when Readable.toWeb() is not supported", () => { - // @ts-expect-error - const originalToWebImpl = Readable.toWeb; - beforeAll(() => { - // @ts-expect-error - Readable.toWeb = undefined; - }); - afterAll(() => { - // @ts-expect-error - Readable.toWeb = originalToWebImpl; - }); - - it("should throw", async () => { - const sdkStream = sdkStreamMixin(passThrough); - try { - sdkStream.transformToWebStream(); - fail(new Error("expect web stream transformation to fail")); - } catch (error) { - expect(error.message).toContain("Readable.toWeb() is not supported"); - } - }); - }); - - describe("when Readable.toWeb() is supported", () => { - // @ts-expect-error - const originalToWebImpl = Readable.toWeb; - beforeAll(() => { - // @ts-expect-error - Readable.toWeb = jest.fn().mockReturnValue("A web stream"); - }); - - afterAll(() => { - // @ts-expect-error - Readable.toWeb = originalToWebImpl; - }); - - it("should tranform Node stream to web stream", async () => { - const sdkStream = sdkStreamMixin(passThrough); - sdkStream.transformToWebStream(); - // @ts-expect-error - expect(Readable.toWeb).toBeCalled(); - }); - - it("should fail any subsequent tranform calls", async () => { - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, [Buffer.from("foo")]); - await sdkStream.transformToWebStream(); - await expectAllTransformsToFail(sdkStream); - }); - }); - }); -}); diff --git a/packages/util-stream-node/src/sdk-stream-mixin.ts b/packages/util-stream-node/src/sdk-stream-mixin.ts deleted file mode 100644 index 4637eb0148b7..000000000000 --- a/packages/util-stream-node/src/sdk-stream-mixin.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { streamCollector } from "@aws-sdk/node-http-handler"; -import { SdkStream, SdkStreamMixin } from "@aws-sdk/types"; -import { fromArrayBuffer } from "@aws-sdk/util-buffer-from"; -import { Readable } from "stream"; -import { TextDecoder } from "util"; - -const ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED = "The stream has already been transformed."; - -/** - * The function that mixes in the utility functions to help consuming runtime-specific payload stream. - * - * @internal - */ -export const sdkStreamMixin = (stream: unknown): SdkStream => { - if (!(stream instanceof Readable)) { - // @ts-ignore - const name = stream?.__proto__?.constructor?.name || stream; - throw new Error(`Unexpected stream implementation, expect Stream.Readable instance, got ${name}`); - } - - let transformed = false; - const transformToByteArray = async () => { - if (transformed) { - throw new Error(ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED); - } - transformed = true; - return await streamCollector(stream); - }; - - return Object.assign(stream, { - transformToByteArray, - transformToString: async (encoding?: string) => { - const buf = await transformToByteArray(); - if (encoding === undefined || Buffer.isEncoding(encoding)) { - return fromArrayBuffer(buf.buffer, buf.byteOffset, buf.byteLength).toString(encoding as BufferEncoding); - } else { - const decoder = new TextDecoder(encoding); - return decoder.decode(buf); - } - }, - transformToWebStream: () => { - if (transformed) { - throw new Error(ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED); - } - if (stream.readableFlowing !== null) { - // Prevent side effect of consuming webstream. - throw new Error("The stream has been consumed by other callbacks."); - } - // @ts-expect-error toWeb() is only available in Node.js >= 17.0.0 - if (typeof Readable.toWeb !== "function") { - throw new Error( - "Readable.toWeb() is not supported. Please make sure you are using Node.js >= 17.0.0, or polyfill is available." - ); - } - transformed = true; - // @ts-expect-error toWeb() is only available in Node.js >= 17.0.0 - return Readable.toWeb(stream); - }, - }); -}; diff --git a/packages/util-stream/jest.config.integ.js b/packages/util-stream/jest.config.integ.js deleted file mode 100644 index d09aba7398c7..000000000000 --- a/packages/util-stream/jest.config.integ.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - preset: "ts-jest", - testMatch: ["**/*.integ.spec.ts"], -}; diff --git a/packages/util-stream/jest.config.js b/packages/util-stream/jest.config.js deleted file mode 100644 index fa2ccdd2f970..000000000000 --- a/packages/util-stream/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, - testPathIgnorePatterns: ["(.*).browser.spec.js"], -}; diff --git a/packages/util-stream/karma.conf.js b/packages/util-stream/karma.conf.js deleted file mode 100644 index f652e2493e6d..000000000000 --- a/packages/util-stream/karma.conf.js +++ /dev/null @@ -1,26 +0,0 @@ -// Set up binary for Chromium browser in CHROME_BIN environment variable before running the test - -module.exports = function (config) { - config.set({ - frameworks: ["jasmine", "karma-typescript"], - files: ["src/getAwsChunkedEncodingStream.browser.ts", "src/getAwsChunkedEncodingStream.browser.spec.ts"], - exclude: ["**/*.d.ts"], - preprocessors: { - "**/*.ts": "karma-typescript", - }, - reporters: ["progress", "karma-typescript"], - browsers: ["ChromeHeadlessNoSandbox"], - customLaunchers: { - ChromeHeadlessNoSandbox: { - base: "ChromeHeadless", - flags: ["--no-sandbox"], - }, - }, - karmaTypescriptConfig: { - bundlerOptions: { - addNodeGlobals: true, - }, - }, - singleRun: true, - }); -}; diff --git a/packages/util-stream/package.json b/packages/util-stream/package.json index f9baa3d51f8b..ab1bf21a68a0 100644 --- a/packages/util-stream/package.json +++ b/packages/util-stream/package.json @@ -9,8 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest && karma start karma.conf.js", - "test:integration": "jest -c jest.config.integ.js" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -21,13 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/fetch-http-handler": "*", - "@aws-sdk/node-http-handler": "*", - "@aws-sdk/types": "*", - "@aws-sdk/util-base64": "*", - "@aws-sdk/util-buffer-from": "*", - "@aws-sdk/util-hex-encoding": "*", - "@aws-sdk/util-utf8": "*", + "@smithy/util-stream": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/util-stream/src/blob/Uint8ArrayBlobAdapter.spec.ts b/packages/util-stream/src/blob/Uint8ArrayBlobAdapter.spec.ts deleted file mode 100644 index d8f071c19654..000000000000 --- a/packages/util-stream/src/blob/Uint8ArrayBlobAdapter.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Uint8ArrayBlobAdapter } from "./Uint8ArrayBlobAdapter"; - -describe(Uint8ArrayBlobAdapter.name, () => { - it("extends Uint8Array", () => { - const blobby = Uint8ArrayBlobAdapter.mutate(new Uint8Array(5)); - - blobby[-1] = 8; - blobby[0] = 8; - blobby[1] = -1; - blobby[3] = 256; - blobby[4] = 8; - blobby[5] = 8; - - // cannot use unallocated index left - expect(blobby[-1]).toEqual(undefined); - expect(blobby[0]).toEqual(8); - // downward overflow - expect(blobby[1]).toEqual(255); - // upward overflow - expect(blobby[3]).toEqual(0); - expect(blobby[4]).toEqual(8); - // cannot use unallocated index right - expect(blobby[5]).toEqual(undefined); - - expect(blobby.length).toEqual(5); - }); - - it("should transform to string synchronously", () => { - const blob = Uint8ArrayBlobAdapter.fromString("test-123"); - expect(blob.transformToString()).toEqual("test-123"); - }); -}); diff --git a/packages/util-stream/src/blob/Uint8ArrayBlobAdapter.ts b/packages/util-stream/src/blob/Uint8ArrayBlobAdapter.ts deleted file mode 100644 index 447be19b21d8..000000000000 --- a/packages/util-stream/src/blob/Uint8ArrayBlobAdapter.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { transformFromString, transformToString } from "./transforms"; - -/** - * Adapter for conversions of the native Uint8Array type. - * @public - */ -export class Uint8ArrayBlobAdapter extends Uint8Array { - /** - * @param source - such as a string or Stream. - * @returns a new Uint8ArrayBlobAdapter extending Uint8Array. - */ - public static fromString(source: string, encoding = "utf-8"): Uint8ArrayBlobAdapter { - switch (typeof source) { - case "string": - return transformFromString(source, encoding); - default: - throw new Error(`Unsupported conversion from ${typeof source} to Uint8ArrayBlobAdapter.`); - } - } - - /** - * @param source - Uint8Array to be mutated. - * @returns the same Uint8Array but with prototype switched to Uint8ArrayBlobAdapter. - */ - public static mutate(source: Uint8Array): Uint8ArrayBlobAdapter { - Object.setPrototypeOf(source, Uint8ArrayBlobAdapter.prototype); - return source as Uint8ArrayBlobAdapter; - } - - /** - * @param encoding - default 'utf-8'. - * @returns the blob as string. - */ - public transformToString(encoding = "utf-8"): string { - return transformToString(this, encoding); - } -} diff --git a/packages/util-stream/src/blob/transforms.ts b/packages/util-stream/src/blob/transforms.ts deleted file mode 100644 index bd05bd5c0e75..000000000000 --- a/packages/util-stream/src/blob/transforms.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { fromBase64, toBase64 } from "@aws-sdk/util-base64"; -import { fromUtf8, toUtf8 } from "@aws-sdk/util-utf8"; - -import { Uint8ArrayBlobAdapter } from "./Uint8ArrayBlobAdapter"; - -/** - * @internal - */ -export function transformToString(payload: Uint8Array, encoding = "utf-8"): string { - if (encoding === "base64") { - return toBase64(payload); - } - return toUtf8(payload); -} - -/** - * @internal - */ -export function transformFromString(str: string, encoding?: string): Uint8ArrayBlobAdapter { - if (encoding === "base64") { - return Uint8ArrayBlobAdapter.mutate(fromBase64(str)); - } - return Uint8ArrayBlobAdapter.mutate(fromUtf8(str)); -} diff --git a/packages/util-stream/src/getAwsChunkedEncodingStream.browser.spec.ts b/packages/util-stream/src/getAwsChunkedEncodingStream.browser.spec.ts deleted file mode 100644 index f6389b97a6b3..000000000000 --- a/packages/util-stream/src/getAwsChunkedEncodingStream.browser.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -// @jest-environment jsdom -import { getAwsChunkedEncodingStream } from "./getAwsChunkedEncodingStream.browser"; - -describe(getAwsChunkedEncodingStream.name, () => { - const mockChecksum = "mockChecksum"; - const mockRawChecksum = Buffer.from(mockChecksum); - const mockStreamChunks = ["Hello", "World"]; - const mockBodyLength = 5; - - const mockBase64Encoder = () => mockChecksum; - const mockBodyLengthChecker = () => mockBodyLength; - const mockChecksumAlgorithmFn = () => {}; - const mockChecksumLocationName = "mockChecksumLocationName"; - const mockStreamHasher = () => mockRawChecksum; - - const mockOptions: any = { - base64Encoder: mockBase64Encoder, - bodyLengthChecker: mockBodyLengthChecker, - checksumAlgorithmFn: mockChecksumAlgorithmFn, - checksumLocationName: mockChecksumLocationName, - streamHasher: mockStreamHasher, - }; - - const getMockReadableStream = () => { - return new ReadableStream({ - start(controller) { - for (const chunk of mockStreamChunks) { - controller.enqueue(chunk); - } - controller.close(); - }, - }); - }; - - const validateStream = async (readableStream: ReadableStream, expectedBuffer: string) => { - let buffer = ""; - const reader = readableStream.getReader(); - while (true) { - const { done, value } = await reader.read(); - if (done) { - break; - } - buffer += value; - } - reader.releaseLock(); - expect(buffer).toEqual(expectedBuffer); - }; - - describe("skips checksum computation", () => { - const expectedBuffer = `5\r -Hello\r -5\r -World\r -0\r -`; - - it("if none of the required options are passed", async () => { - const readableStream = getMockReadableStream(); - const awsChunkedBody = getAwsChunkedEncodingStream(readableStream, { - bodyLengthChecker: mockBodyLengthChecker, - }); - await validateStream(awsChunkedBody, expectedBuffer); - }); - - ["base64Encoder", "checksumAlgorithmFn", "checksumLocationName", "streamHasher"].forEach((optionToRemove) => { - it(`if option '${optionToRemove} is not passed`, async () => { - const readableStream = getMockReadableStream(); - const awsChunkedBody = getAwsChunkedEncodingStream(readableStream, { - ...mockOptions, - [optionToRemove]: undefined, - }); - await validateStream(awsChunkedBody, expectedBuffer); - }); - }); - }); - - it("computes checksum and adds it to the end event", async () => { - const readableStream = getMockReadableStream(); - const awsChunkedBody = getAwsChunkedEncodingStream(readableStream, mockOptions); - const expectedBuffer = `5\r -Hello\r -5\r -World\r -0\r -${mockChecksumLocationName}:${mockChecksum}\r -\r -`; - await validateStream(awsChunkedBody, expectedBuffer); - }); -}); diff --git a/packages/util-stream/src/getAwsChunkedEncodingStream.browser.ts b/packages/util-stream/src/getAwsChunkedEncodingStream.browser.ts deleted file mode 100644 index 12c47b70b612..000000000000 --- a/packages/util-stream/src/getAwsChunkedEncodingStream.browser.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { GetAwsChunkedEncodingStream, GetAwsChunkedEncodingStreamOptions } from "@aws-sdk/types"; - -/** - * @internal - */ -export const getAwsChunkedEncodingStream: GetAwsChunkedEncodingStream = ( - readableStream: ReadableStream, - options: GetAwsChunkedEncodingStreamOptions -) => { - const { base64Encoder, bodyLengthChecker, checksumAlgorithmFn, checksumLocationName, streamHasher } = options; - - const checksumRequired = - base64Encoder !== undefined && - bodyLengthChecker !== undefined && - checksumAlgorithmFn !== undefined && - checksumLocationName !== undefined && - streamHasher !== undefined; - const digest = checksumRequired ? streamHasher!(checksumAlgorithmFn!, readableStream) : undefined; - - // ToDo: Validate the ReadableStream and getReader() is accessable before calling. - // ReactNative doesn't support ReadableStream. They might not be available in older browsers, or some polyfills. - const reader = readableStream.getReader(); - return new ReadableStream({ - async pull(controller) { - const { value, done } = await reader.read(); - - if (done) { - controller.enqueue(`0\r\n`); - if (checksumRequired) { - const checksum = base64Encoder!(await digest!); - controller.enqueue(`${checksumLocationName}:${checksum}\r\n`); - controller.enqueue(`\r\n`); - } - controller.close(); - } else { - controller.enqueue(`${(bodyLengthChecker(value) || 0).toString(16)}\r\n${value}\r\n`); - } - }, - }); -}; diff --git a/packages/util-stream/src/getAwsChunkedEncodingStream.spec.ts b/packages/util-stream/src/getAwsChunkedEncodingStream.spec.ts deleted file mode 100644 index d593a5db7422..000000000000 --- a/packages/util-stream/src/getAwsChunkedEncodingStream.spec.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Readable } from "stream"; - -import { getAwsChunkedEncodingStream } from "./getAwsChunkedEncodingStream"; - -describe(getAwsChunkedEncodingStream.name, () => { - const mockBase64Encoder = jest.fn(); - const mockBodyLengthChecker = jest.fn(); - const mockChecksumAlgorithmFn = jest.fn(); - const mockChecksumLocationName = "mockChecksumLocationName"; - const mockStreamHasher = jest.fn(); - - const mockOptions = { - base64Encoder: mockBase64Encoder, - bodyLengthChecker: mockBodyLengthChecker, - checksumAlgorithmFn: mockChecksumAlgorithmFn, - checksumLocationName: mockChecksumLocationName, - streamHasher: mockStreamHasher, - }; - - const mockChecksum = "mockChecksum"; - const mockRawChecksum = Buffer.from(mockChecksum); - const mockStreamChunks = ["Hello", "World"]; - const mockBodyLength = 5; - - const getMockReadableStream = () => { - const readableStream = new Readable(); - mockStreamChunks.forEach((chunk) => { - readableStream.push(chunk); - }); - readableStream.push(null); - return readableStream; - }; - - beforeEach(() => { - mockStreamHasher.mockResolvedValue(mockRawChecksum); - mockBase64Encoder.mockReturnValue(mockChecksum); - mockBodyLengthChecker.mockReturnValue(mockBodyLength); - }); - - afterEach(() => { - expect(mockBodyLengthChecker).toHaveBeenCalledTimes(mockStreamChunks.length); - mockStreamChunks.forEach((chunk, index) => { - expect(mockBodyLengthChecker).toHaveBeenNthCalledWith(index + 1, Buffer.from(chunk)); - }); - jest.clearAllMocks(); - }); - - describe("skips checksum computation", () => { - const validateStreamWithoutChecksum = (awsChunkedEncodingStream: Readable, done: Function) => { - let buffer = ""; - awsChunkedEncodingStream.on("data", (data) => { - buffer += data.toString(); - }); - awsChunkedEncodingStream.on("end", () => { - expect(mockStreamHasher).not.toHaveBeenCalled(); - expect(mockBase64Encoder).not.toHaveBeenCalled(); - expect(buffer).toEqual(`5\r -Hello\r -5\r -World\r -0\r -`); - done(); - }); - }; - - it("if none of the required options are passed", (done) => { - const readableStream = getMockReadableStream(); - const awsChunkedEncodingStream = getAwsChunkedEncodingStream(readableStream, { - bodyLengthChecker: mockBodyLengthChecker, - }); - validateStreamWithoutChecksum(awsChunkedEncodingStream, done); - }); - - ["base64Encoder", "checksumAlgorithmFn", "checksumLocationName", "streamHasher"].forEach((optionToRemove) => { - it(`if option '${optionToRemove}' is not passed`, (done) => { - const readableStream = getMockReadableStream(); - const awsChunkedEncodingStream = getAwsChunkedEncodingStream(readableStream, { - ...mockOptions, - [optionToRemove]: undefined, - }); - validateStreamWithoutChecksum(awsChunkedEncodingStream, done); - }); - }); - }); - - it("computes checksum and adds it to the end event", (done) => { - const readableStream = getMockReadableStream(); - const awsChunkedEncodingStream = getAwsChunkedEncodingStream(readableStream, mockOptions); - - let buffer = ""; - awsChunkedEncodingStream.on("data", (data) => { - buffer += data.toString(); - }); - awsChunkedEncodingStream.on("end", () => { - expect(mockStreamHasher).toHaveBeenCalledWith(mockChecksumAlgorithmFn, readableStream); - expect(mockBase64Encoder).toHaveBeenCalledWith(mockRawChecksum); - expect(buffer).toStrictEqual(`5\r -Hello\r -5\r -World\r -0\r -mockChecksumLocationName:mockChecksum\r -\r -`); - done(); - }); - }); -}); diff --git a/packages/util-stream/src/getAwsChunkedEncodingStream.ts b/packages/util-stream/src/getAwsChunkedEncodingStream.ts deleted file mode 100644 index 27595267564c..000000000000 --- a/packages/util-stream/src/getAwsChunkedEncodingStream.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { GetAwsChunkedEncodingStream, GetAwsChunkedEncodingStreamOptions } from "@aws-sdk/types"; -import { Readable } from "stream"; - -/** - * @internal - */ -export const getAwsChunkedEncodingStream: GetAwsChunkedEncodingStream = ( - readableStream: Readable, - options: GetAwsChunkedEncodingStreamOptions -) => { - const { base64Encoder, bodyLengthChecker, checksumAlgorithmFn, checksumLocationName, streamHasher } = options; - - const checksumRequired = - base64Encoder !== undefined && - checksumAlgorithmFn !== undefined && - checksumLocationName !== undefined && - streamHasher !== undefined; - const digest = checksumRequired ? streamHasher!(checksumAlgorithmFn!, readableStream) : undefined; - - const awsChunkedEncodingStream = new Readable({ read: () => {} }); - readableStream.on("data", (data) => { - const length = bodyLengthChecker(data) || 0; - awsChunkedEncodingStream.push(`${length.toString(16)}\r\n`); - awsChunkedEncodingStream.push(data); - awsChunkedEncodingStream.push("\r\n"); - }); - readableStream.on("end", async () => { - awsChunkedEncodingStream.push(`0\r\n`); - if (checksumRequired) { - const checksum = base64Encoder!(await digest!); - awsChunkedEncodingStream.push(`${checksumLocationName}:${checksum}\r\n`); - awsChunkedEncodingStream.push(`\r\n`); - } - awsChunkedEncodingStream.push(null); - }); - return awsChunkedEncodingStream; -}; diff --git a/packages/util-stream/src/index.ts b/packages/util-stream/src/index.ts index 47bcb14b718f..3b628dea49d0 100644 --- a/packages/util-stream/src/index.ts +++ b/packages/util-stream/src/index.ts @@ -1,3 +1 @@ -export * from "./blob/Uint8ArrayBlobAdapter"; -export * from "./getAwsChunkedEncodingStream"; -export * from "./sdk-stream-mixin"; +export * from "@smithy/util-stream"; diff --git a/packages/util-stream/src/sdk-stream-mixin.browser.spec.ts b/packages/util-stream/src/sdk-stream-mixin.browser.spec.ts deleted file mode 100644 index 697ad2bf0694..000000000000 --- a/packages/util-stream/src/sdk-stream-mixin.browser.spec.ts +++ /dev/null @@ -1,222 +0,0 @@ -// @jest-environment jsdom -import { streamCollector } from "@aws-sdk/fetch-http-handler"; -import { SdkStreamMixin } from "@aws-sdk/types"; -import { toBase64 } from "@aws-sdk/util-base64"; -import { toHex } from "@aws-sdk/util-hex-encoding"; -import { toUtf8 } from "@aws-sdk/util-utf8"; - -import { sdkStreamMixin } from "./sdk-stream-mixin.browser"; - -jest.mock("@aws-sdk/fetch-http-handler"); -jest.mock("@aws-sdk/util-base64"); -jest.mock("@aws-sdk/util-hex-encoding"); -jest.mock("@aws-sdk/util-utf8"); - -const mockStreamCollectorReturn = Uint8Array.from([117, 112, 113]); -(streamCollector as jest.Mock).mockReturnValue(mockStreamCollectorReturn); - -describe(sdkStreamMixin.name, () => { - const expectAllTransformsToFail = async (sdkStream: SdkStreamMixin) => { - const transformMethods: Array = [ - "transformToByteArray", - "transformToString", - "transformToWebStream", - ]; - for (const method of transformMethods) { - try { - await sdkStream[method](); - fail(new Error("expect subsequent tranform to fail")); - } catch (error) { - expect(error.message).toContain("The stream has already been transformed"); - } - } - }; - - let originalReadableStreamCtr = global.ReadableStream; - const mockReadableStream = jest.fn(); - class ReadableStream { - constructor() { - mockReadableStream(); - } - } - - let payloadStream: ReadableStream; - - beforeAll(() => { - global.ReadableStream = ReadableStream as any; - }); - - beforeEach(() => { - originalReadableStreamCtr = global.ReadableStream; - jest.clearAllMocks(); - payloadStream = new ReadableStream(); - }); - - afterEach(() => { - global.ReadableStream = originalReadableStreamCtr; - }); - - it("should throw if input stream is not a Blob or Web Stream instance", () => { - const originalBlobCtr = global.Blob; - global.Blob = undefined; - global.ReadableStream = undefined; - try { - sdkStreamMixin({}); - fail("expect unexpected stream to fail"); - } catch (e) { - expect(e.message).toContain("nexpected stream implementation"); - global.Blob = originalBlobCtr; - } - }); - - describe("transformToByteArray", () => { - it("should transform binary stream to byte array", async () => { - const sdkStream = sdkStreamMixin(payloadStream); - const byteArray = await sdkStream.transformToByteArray(); - expect(streamCollector as jest.Mock).toBeCalledWith(payloadStream); - expect(byteArray).toEqual(mockStreamCollectorReturn); - }); - - it("should fail any subsequent tranform calls", async () => { - const sdkStream = sdkStreamMixin(payloadStream); - await sdkStream.transformToByteArray(); - await expectAllTransformsToFail(sdkStream); - }); - }); - - describe("transformToString", () => { - let originalTextDecoder = global.TextDecoder; - const mockDecode = jest.fn(); - global.TextDecoder = jest.fn().mockImplementation(function () { - return { decode: mockDecode }; - }); - - beforeEach(() => { - originalTextDecoder = global.TextDecoder; - jest.clearAllMocks(); - }); - - afterEach(() => { - global.TextDecoder = originalTextDecoder; - }); - - it.each([ - [undefined, toUtf8], - ["utf8", toUtf8], - ["utf-8", toUtf8], - ["base64", toBase64], - ["hex", toHex], - ])("should transform to string with %s encoding", async (encoding, encodingFn) => { - const mockEncodedStringValue = `a string with ${encoding} encoding`; - (encodingFn as jest.Mock).mockReturnValueOnce(mockEncodedStringValue); - const sdkStream = sdkStreamMixin(payloadStream); - const str = await sdkStream.transformToString(encoding); - expect(streamCollector).toBeCalled(); - expect(encodingFn).toBeCalledWith(mockStreamCollectorReturn); - expect(str).toEqual(mockEncodedStringValue); - }); - - it("should use TexDecoder to handle other encodings", async () => { - const utfLabel = "windows-1251"; - mockDecode.mockReturnValue(`a string with ${utfLabel} encoding`); - const sdkStream = sdkStreamMixin(payloadStream); - const str = await sdkStream.transformToString(utfLabel); - expect(global.TextDecoder).toBeCalledWith(utfLabel); - expect(str).toEqual(`a string with ${utfLabel} encoding`); - }); - - it("should throw if TextDecoder is not available", async () => { - global.TextDecoder = null; - const utfLabel = "windows-1251"; - const sdkStream = sdkStreamMixin(payloadStream); - try { - await sdkStream.transformToString(utfLabel); - fail("expect transformToString to throw when TextDecoder is not available"); - } catch (error) { - expect(error.message).toContain("TextDecoder is not available"); - } - }); - - it("should fail any subsequent tranform calls", async () => { - const sdkStream = sdkStreamMixin(payloadStream); - await sdkStream.transformToString(); - await expectAllTransformsToFail(sdkStream); - }); - }); - - describe("transformToWebStream with ReadableStream payload", () => { - it("should return the payload if it is Web Stream instance", () => { - const payloadStream = new ReadableStream(); - const sdkStream = sdkStreamMixin(payloadStream as any); - const transformed = sdkStream.transformToWebStream(); - expect(transformed).toBe(payloadStream); - }); - - it("should fail any subsequent tranform calls", async () => { - const payloadStream = new ReadableStream(); - const sdkStream = sdkStreamMixin(payloadStream as any); - sdkStream.transformToWebStream(); - await expectAllTransformsToFail(sdkStream); - }); - }); - - describe("transformToWebStream with Blob payload", () => { - let originalBlobCtr = global.Blob; - const mockBlob = jest.fn(); - const mockBlobStream = jest.fn(); - class Blob { - constructor() { - mockBlob(); - } - - stream() { - return mockBlobStream(); - } - } - global.Blob = Blob as any; - - beforeEach(() => { - global.ReadableStream = undefined; - originalBlobCtr = global.Blob; - jest.clearAllMocks(); - }); - - afterEach(() => { - global.Blob = originalBlobCtr; - }); - - it("should transform blob to web stream with Blob.stream()", () => { - mockBlobStream.mockReturnValue("transformed"); - const payloadStream = new Blob(); - const sdkStream = sdkStreamMixin(payloadStream as any); - const transformed = sdkStream.transformToWebStream(); - expect(transformed).toBe("transformed"); - expect(mockBlobStream).toBeCalled(); - }); - - it("should fail if Blob.stream() is not available", async () => { - class Blob { - constructor() { - mockBlob(); - } - } - - global.Blob = Blob as any; - const payloadStream = new Blob(); - const sdkStream = sdkStreamMixin(payloadStream as any); - try { - sdkStream.transformToWebStream(); - fail("expect to fail"); - } catch (e) { - expect(e.message).toContain("Please make sure the Blob.stream() is polyfilled"); - } - }); - - it("should fail any subsequent tranform calls", async () => { - const payloadStream = new Blob(); - const sdkStream = sdkStreamMixin(payloadStream as any); - sdkStream.transformToWebStream(); - await expectAllTransformsToFail(sdkStream); - }); - }); -}); diff --git a/packages/util-stream/src/sdk-stream-mixin.browser.ts b/packages/util-stream/src/sdk-stream-mixin.browser.ts deleted file mode 100644 index d5bdebd93d12..000000000000 --- a/packages/util-stream/src/sdk-stream-mixin.browser.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { streamCollector } from "@aws-sdk/fetch-http-handler"; -import { SdkStream, SdkStreamMixin } from "@aws-sdk/types"; -import { toBase64 } from "@aws-sdk/util-base64"; -import { toHex } from "@aws-sdk/util-hex-encoding"; -import { toUtf8 } from "@aws-sdk/util-utf8"; - -const ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED = "The stream has already been transformed."; - -/** - * The stream handling utility functions for browsers and React Native - * - * @internal - */ -export const sdkStreamMixin = (stream: unknown): SdkStream => { - if (!isBlobInstance(stream) && !isReadableStreamInstance(stream)) { - //@ts-ignore - const name = stream?.__proto__?.constructor?.name || stream; - throw new Error(`Unexpected stream implementation, expect Blob or ReadableStream, got ${name}`); - } - - let transformed = false; - const transformToByteArray = async () => { - if (transformed) { - throw new Error(ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED); - } - transformed = true; - return await streamCollector(stream); - }; - - const blobToWebStream = (blob: Blob) => { - if (typeof blob.stream !== "function") { - throw new Error( - "Cannot transform payload Blob to web stream. Please make sure the Blob.stream() is polyfilled.\n" + - "If you are using React Native, this API is not yet supported, see: https://react-native.canny.io/feature-requests/p/fetch-streaming-body" - ); - } - return blob.stream(); - }; - - return Object.assign(stream, { - transformToByteArray: transformToByteArray, - - transformToString: async (encoding?: string) => { - const buf = await transformToByteArray(); - if (encoding === "base64") { - return toBase64(buf); - } else if (encoding === "hex") { - return toHex(buf); - } else if (encoding === undefined || encoding === "utf8" || encoding === "utf-8") { - // toUtf8() itself will use TextDecoder and fallback to pure JS implementation. - return toUtf8(buf); - } else if (typeof TextDecoder === "function") { - return new TextDecoder(encoding).decode(buf); - } else { - throw new Error("TextDecoder is not available, please make sure polyfill is provided."); - } - }, - - transformToWebStream: () => { - if (transformed) { - throw new Error(ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED); - } - transformed = true; - if (isBlobInstance(stream)) { - // ReadableStream is undefined in React Native - return blobToWebStream(stream); - } else if (isReadableStreamInstance(stream)) { - return stream; - } else { - throw new Error(`Cannot transform payload to web stream, got ${stream}`); - } - }, - }); -}; - -const isBlobInstance = (stream: unknown): stream is Blob => 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/sdk-stream-mixin.spec.ts b/packages/util-stream/src/sdk-stream-mixin.spec.ts deleted file mode 100644 index c3ee5128583b..000000000000 --- a/packages/util-stream/src/sdk-stream-mixin.spec.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { SdkStreamMixin } from "@aws-sdk/types"; -import { fromArrayBuffer } from "@aws-sdk/util-buffer-from"; -import { PassThrough, Readable, Writable } from "stream"; -import util from "util"; - -import { sdkStreamMixin } from "./sdk-stream-mixin"; - -jest.mock("@aws-sdk/util-buffer-from"); - -describe(sdkStreamMixin.name, () => { - const writeDataToStream = (stream: Writable, data: Array): Promise => - new Promise((resolve, reject) => { - data.forEach((chunk) => { - stream.write(chunk, (err) => { - if (err) reject(err); - }); - }); - stream.end(resolve); - }); - const byteArrayFromBuffer = (buf: Buffer) => new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); - let passThrough: PassThrough; - const expectAllTransformsToFail = async (sdkStream: SdkStreamMixin) => { - const transformMethods: Array = [ - "transformToByteArray", - "transformToString", - "transformToWebStream", - ]; - for (const method of transformMethods) { - try { - await sdkStream[method](); - fail(new Error("expect subsequent tranform to fail")); - } catch (error) { - expect(error.message).toContain("The stream has already been transformed"); - } - } - }; - - beforeEach(() => { - passThrough = new PassThrough(); - }); - - it("should throw if unexpected stream implementation is supplied", () => { - try { - const payload = {}; - sdkStreamMixin(payload); - fail("should throw when unexpected stream is supplied"); - } catch (error) { - expect(error.message).toContain("Unexpected stream implementation"); - } - }); - - describe("transformToByteArray", () => { - it("should transform binary stream to byte array", async () => { - const mockData = [Buffer.from("foo"), Buffer.from("bar"), Buffer.from("buzz")]; - const expected = byteArrayFromBuffer(Buffer.from("foobarbuzz")); - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, mockData); - expect(await sdkStream.transformToByteArray()).toEqual(expected); - }); - - it("should fail any subsequent tranform calls", async () => { - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, [Buffer.from("abc")]); - expect(await sdkStream.transformToByteArray()).toEqual(byteArrayFromBuffer(Buffer.from("abc"))); - await expectAllTransformsToFail(sdkStream); - }); - }); - - describe("transformToString", () => { - const toStringMock = jest.fn(); - beforeAll(() => { - jest.resetAllMocks(); - }); - - it("should transform the stream to string with utf-8 encoding by default", async () => { - (fromArrayBuffer as jest.Mock).mockImplementation( - jest.requireActual("@aws-sdk/util-buffer-from").fromArrayBuffer - ); - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, [Buffer.from("foo")]); - const transformed = await sdkStream.transformToString(); - expect(transformed).toEqual("foo"); - }); - - it.each([undefined, "utf-8", "ascii", "base64", "latin1", "binary"])( - "should transform the stream to string with %s encoding", - async (encoding) => { - (fromArrayBuffer as jest.Mock).mockReturnValue({ toString: toStringMock }); - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, [Buffer.from("foo")]); - await sdkStream.transformToString(encoding); - expect(toStringMock).toBeCalledWith(encoding); - } - ); - - it.each(["ibm866", "iso-8859-2", "koi8-r", "macintosh", "windows-874", "gbk", "gb18030", "euc-jp"])( - "should transform the stream to string with TextDecoder config %s", - async (encoding) => { - jest.spyOn(util, "TextDecoder").mockImplementation( - () => - ({ - decode: jest.fn(), - } as any) - ); - (fromArrayBuffer as jest.Mock).mockReturnValue({ toString: toStringMock }); - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, [Buffer.from("foo")]); - await sdkStream.transformToString(encoding as BufferEncoding); - expect(util.TextDecoder).toBeCalledWith(encoding); - } - ); - - it("should fail any subsequent tranform calls", async () => { - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, [Buffer.from("foo")]); - await sdkStream.transformToString(); - await expectAllTransformsToFail(sdkStream); - }); - }); - - describe("transformToWebStream", () => { - it("should throw if any event listener is attached on the underlying stream", async () => { - passThrough.on("data", console.log); - const sdkStream = sdkStreamMixin(passThrough); - try { - sdkStream.transformToWebStream(); - fail(new Error("expect web stream transformation to fail")); - } catch (error) { - expect(error.message).toContain("The stream has been consumed by other callbacks"); - } - }); - - describe("when Readable.toWeb() is not supported", () => { - // @ts-expect-error - const originalToWebImpl = Readable.toWeb; - beforeAll(() => { - // @ts-expect-error - Readable.toWeb = undefined; - }); - afterAll(() => { - // @ts-expect-error - Readable.toWeb = originalToWebImpl; - }); - - it("should throw", async () => { - const sdkStream = sdkStreamMixin(passThrough); - try { - sdkStream.transformToWebStream(); - fail(new Error("expect web stream transformation to fail")); - } catch (error) { - expect(error.message).toContain("Readable.toWeb() is not supported"); - } - }); - }); - - describe("when Readable.toWeb() is supported", () => { - // @ts-expect-error - const originalToWebImpl = Readable.toWeb; - beforeAll(() => { - // @ts-expect-error - Readable.toWeb = jest.fn().mockReturnValue("A web stream"); - }); - - afterAll(() => { - // @ts-expect-error - Readable.toWeb = originalToWebImpl; - }); - - it("should tranform Node stream to web stream", async () => { - const sdkStream = sdkStreamMixin(passThrough); - sdkStream.transformToWebStream(); - // @ts-expect-error - expect(Readable.toWeb).toBeCalled(); - }); - - it("should fail any subsequent tranform calls", async () => { - const sdkStream = sdkStreamMixin(passThrough); - await writeDataToStream(passThrough, [Buffer.from("foo")]); - await sdkStream.transformToWebStream(); - await expectAllTransformsToFail(sdkStream); - }); - }); - }); -}); diff --git a/packages/util-stream/src/sdk-stream-mixin.ts b/packages/util-stream/src/sdk-stream-mixin.ts deleted file mode 100644 index 4637eb0148b7..000000000000 --- a/packages/util-stream/src/sdk-stream-mixin.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { streamCollector } from "@aws-sdk/node-http-handler"; -import { SdkStream, SdkStreamMixin } from "@aws-sdk/types"; -import { fromArrayBuffer } from "@aws-sdk/util-buffer-from"; -import { Readable } from "stream"; -import { TextDecoder } from "util"; - -const ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED = "The stream has already been transformed."; - -/** - * The function that mixes in the utility functions to help consuming runtime-specific payload stream. - * - * @internal - */ -export const sdkStreamMixin = (stream: unknown): SdkStream => { - if (!(stream instanceof Readable)) { - // @ts-ignore - const name = stream?.__proto__?.constructor?.name || stream; - throw new Error(`Unexpected stream implementation, expect Stream.Readable instance, got ${name}`); - } - - let transformed = false; - const transformToByteArray = async () => { - if (transformed) { - throw new Error(ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED); - } - transformed = true; - return await streamCollector(stream); - }; - - return Object.assign(stream, { - transformToByteArray, - transformToString: async (encoding?: string) => { - const buf = await transformToByteArray(); - if (encoding === undefined || Buffer.isEncoding(encoding)) { - return fromArrayBuffer(buf.buffer, buf.byteOffset, buf.byteLength).toString(encoding as BufferEncoding); - } else { - const decoder = new TextDecoder(encoding); - return decoder.decode(buf); - } - }, - transformToWebStream: () => { - if (transformed) { - throw new Error(ERR_MSG_STREAM_HAS_BEEN_TRANSFORMED); - } - if (stream.readableFlowing !== null) { - // Prevent side effect of consuming webstream. - throw new Error("The stream has been consumed by other callbacks."); - } - // @ts-expect-error toWeb() is only available in Node.js >= 17.0.0 - if (typeof Readable.toWeb !== "function") { - throw new Error( - "Readable.toWeb() is not supported. Please make sure you are using Node.js >= 17.0.0, or polyfill is available." - ); - } - transformed = true; - // @ts-expect-error toWeb() is only available in Node.js >= 17.0.0 - return Readable.toWeb(stream); - }, - }); -}; diff --git a/packages/util-uri-escape/jest.config.js b/packages/util-uri-escape/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/util-uri-escape/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/util-uri-escape/package.json b/packages/util-uri-escape/package.json index d4c1d96d78ab..a5fb38b308f7 100644 --- a/packages/util-uri-escape/package.json +++ b/packages/util-uri-escape/package.json @@ -9,7 +9,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", @@ -20,6 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { + "@smithy/util-uri-escape": "^1.0.1", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/util-uri-escape/src/escape-uri-path.spec.ts b/packages/util-uri-escape/src/escape-uri-path.spec.ts deleted file mode 100644 index 4805bc4d2ca4..000000000000 --- a/packages/util-uri-escape/src/escape-uri-path.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { escapeUriPath } from "./escape-uri-path"; - -describe("escapeUriPath", () => { - it(`does not escape '/'`, () => { - expect(escapeUriPath("/a/path/to/nowhere")).toBe("/a/path/to/nowhere"); - }); - - it("does escape non-forward slash characters", () => { - expect(escapeUriPath("/once/more !")).toBe("/once/more%20%21"); - }); -}); diff --git a/packages/util-uri-escape/src/escape-uri-path.ts b/packages/util-uri-escape/src/escape-uri-path.ts deleted file mode 100644 index 76098b3a6dfb..000000000000 --- a/packages/util-uri-escape/src/escape-uri-path.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { escapeUri } from "./escape-uri"; - -/** - * @internal - */ -export const escapeUriPath = (uri: string): string => uri.split("/").map(escapeUri).join("/"); diff --git a/packages/util-uri-escape/src/escape-uri.spec.ts b/packages/util-uri-escape/src/escape-uri.spec.ts deleted file mode 100644 index c256c35601ab..000000000000 --- a/packages/util-uri-escape/src/escape-uri.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { escapeUri } from "./escape-uri"; - -describe("escapeUri", () => { - it("should convert most special characters", () => { - expect(escapeUri("!@#$%^&*();':\"{}[],/?`")).toBe( - "%21%40%23%24%25%5E%26%2A%28%29%3B%27%3A%22%7B%7D%5B%5D%2C%2F%3F%60" - ); - }); - - it("should not convert tildas or periods", () => { - expect(escapeUri("~.")).toBe("~."); - }); - - it("should encode spaces as %20", () => { - expect(escapeUri(" ")).toBe("%20"); - }); - - it("should convert reserved characters", () => { - expect(escapeUri(`!'()*`)).toBe("%21%27%28%29%2A"); - }); -}); diff --git a/packages/util-uri-escape/src/escape-uri.ts b/packages/util-uri-escape/src/escape-uri.ts deleted file mode 100644 index fbc194572dbc..000000000000 --- a/packages/util-uri-escape/src/escape-uri.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @internal - */ -export const escapeUri = (uri: string): string => - // AWS percent-encodes some extra non-standard characters in a URI - encodeURIComponent(uri).replace(/[!'()*]/g, hexEncode); - -const hexEncode = (c: string) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`; diff --git a/packages/util-uri-escape/src/index.ts b/packages/util-uri-escape/src/index.ts index 1913825c219d..330875a24f0f 100644 --- a/packages/util-uri-escape/src/index.ts +++ b/packages/util-uri-escape/src/index.ts @@ -1,8 +1 @@ -/** - * @internal - */ -export * from "./escape-uri"; -/** - * @internal - */ -export * from "./escape-uri-path"; +export * from "@smithy/util-uri-escape"; diff --git a/packages/util-utf8/jest.config.js b/packages/util-utf8/jest.config.js deleted file mode 100644 index 95d8863b22a1..000000000000 --- a/packages/util-utf8/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, - testMatch: ["**/*.spec.ts"], -}; diff --git a/packages/util-utf8/package.json b/packages/util-utf8/package.json index a34dddf8431a..b7c6a2ba8f1c 100644 --- a/packages/util-utf8/package.json +++ b/packages/util-utf8/package.json @@ -12,7 +12,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", @@ -20,7 +20,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/util-buffer-from": "*", + "@smithy/util-utf8": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/util-utf8/src/fromUtf8.browser.spec.ts b/packages/util-utf8/src/fromUtf8.browser.spec.ts deleted file mode 100644 index 4d97df926109..000000000000 --- a/packages/util-utf8/src/fromUtf8.browser.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @jest-environment jsdom - */ -import { fromUtf8 } from "./fromUtf8.browser"; - -declare const global: any; - -describe("fromUtf8", () => { - it("should use the Encoding API", () => { - const expected = new Uint8Array(0); - const encode = jest.fn().mockReturnValue(expected); - (global as any).TextEncoder = jest.fn(() => ({ encode })); - - expect(fromUtf8("ABC")).toBe(expected); - }); -}); diff --git a/packages/util-utf8/src/fromUtf8.browser.ts b/packages/util-utf8/src/fromUtf8.browser.ts deleted file mode 100644 index f5352f0f495d..000000000000 --- a/packages/util-utf8/src/fromUtf8.browser.ts +++ /dev/null @@ -1 +0,0 @@ -export const fromUtf8 = (input: string): Uint8Array => new TextEncoder().encode(input); diff --git a/packages/util-utf8/src/fromUtf8.spec.ts b/packages/util-utf8/src/fromUtf8.spec.ts deleted file mode 100644 index 5cc02b88e9b0..000000000000 --- a/packages/util-utf8/src/fromUtf8.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { fromUtf8 } from "./fromUtf8"; - -const utf8StringsToByteArrays: Record = { - ABC: new Uint8Array(["A".charCodeAt(0), "B".charCodeAt(0), "C".charCodeAt(0)]), - "🐎👱❤": new Uint8Array([240, 159, 144, 142, 240, 159, 145, 177, 226, 157, 164]), - "☃💩": new Uint8Array([226, 152, 131, 240, 159, 146, 169]), - "The rain in Spain falls mainly on the plain.": new Uint8Array([ - 84, 104, 101, 32, 114, 97, 105, 110, 32, 105, 110, 32, 83, 112, 97, 105, 110, 32, 102, 97, 108, 108, 115, 32, 109, - 97, 105, 110, 108, 121, 32, 111, 110, 32, 116, 104, 101, 32, 112, 108, 97, 105, 110, 46, - ]), - "دست‌نوشته‌ها نمی‌سوزند": new Uint8Array([ - 216, 175, 216, 179, 216, 170, 226, 128, 140, 217, 134, 217, 136, 216, 180, 216, 170, 217, 135, 226, 128, 140, 217, - 135, 216, 167, 32, 217, 134, 217, 133, 219, 140, 226, 128, 140, 216, 179, 217, 136, 216, 178, 217, 134, 216, 175, - ]), - "Рукописи не горят": new Uint8Array([ - 208, 160, 209, 131, 208, 186, 208, 190, 208, 191, 208, 184, 209, 129, 208, 184, 32, 208, 189, 208, 181, 32, 208, - 179, 208, 190, 209, 128, 209, 143, 209, 130, - ]), -}; - -describe("fromUtf8", () => { - for (const string of Object.keys(utf8StringsToByteArrays)) { - it(`should UTF-8 decode "${string}" to the correct value`, () => { - expect(fromUtf8(string)).toEqual(utf8StringsToByteArrays[string]); - }); - } - - it("should throw when given a number", () => { - expect(() => fromUtf8(255 as any)).toThrow(); - }); -}); diff --git a/packages/util-utf8/src/fromUtf8.ts b/packages/util-utf8/src/fromUtf8.ts deleted file mode 100644 index a0d1707b06d9..000000000000 --- a/packages/util-utf8/src/fromUtf8.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { fromString } from "@aws-sdk/util-buffer-from"; - -export const fromUtf8 = (input: string): Uint8Array => { - const buf = fromString(input, "utf8"); - return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength / Uint8Array.BYTES_PER_ELEMENT); -}; diff --git a/packages/util-utf8/src/index.ts b/packages/util-utf8/src/index.ts index 00ba46574e0e..19d778513718 100644 --- a/packages/util-utf8/src/index.ts +++ b/packages/util-utf8/src/index.ts @@ -1,3 +1 @@ -export * from "./fromUtf8"; -export * from "./toUint8Array"; -export * from "./toUtf8"; +export * from "@smithy/util-utf8"; diff --git a/packages/util-utf8/src/toUint8Array.spec.ts b/packages/util-utf8/src/toUint8Array.spec.ts deleted file mode 100644 index cff395d2759e..000000000000 --- a/packages/util-utf8/src/toUint8Array.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { toUint8Array } from "./toUint8Array"; - -describe("toUint8Array", () => { - it(`should convert string to Uint8Array`, () => { - const expected = new Uint8Array([240, 159, 144, 142, 240, 159, 145, 177, 226, 157, 164]); - expect(toUint8Array("🐎👱❤")).toStrictEqual(expected); - }); - it(`should convert buffer to Uint8Array`, () => { - const expected = new Uint8Array([240, 159, 144, 142, 240, 159, 145, 177, 226, 157, 164]); - const buffer = Buffer.from(expected); - expect(toUint8Array(buffer)).toStrictEqual(expected); - }); - it(`should convert ArrayBuffer to Uint8Array`, () => { - const input = new Uint32Array([240]); - const expected = new Uint8Array([240, 0, 0, 0]); - expect(toUint8Array(input)).toStrictEqual(expected); - }); -}); diff --git a/packages/util-utf8/src/toUint8Array.ts b/packages/util-utf8/src/toUint8Array.ts deleted file mode 100644 index fdd1f05dd667..000000000000 --- a/packages/util-utf8/src/toUint8Array.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { fromUtf8 } from "./fromUtf8"; - -export const toUint8Array = (data: string | ArrayBuffer | ArrayBufferView): Uint8Array => { - if (typeof data === "string") { - return fromUtf8(data); - } - - if (ArrayBuffer.isView(data)) { - return new Uint8Array(data.buffer, data.byteOffset, data.byteLength / Uint8Array.BYTES_PER_ELEMENT); - } - - return new Uint8Array(data); -}; diff --git a/packages/util-utf8/src/toUtf8.browser.spec.ts b/packages/util-utf8/src/toUtf8.browser.spec.ts deleted file mode 100644 index f51d5bcd0162..000000000000 --- a/packages/util-utf8/src/toUtf8.browser.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @jest-environment jsdom - */ -import { toUtf8 } from "./toUtf8.browser"; - -declare const global: any; - -describe("toUtf8", () => { - it("should use the Encoding API", () => { - const expected = "ABC"; - const decode = jest.fn().mockReturnValue(expected); - (global as any).TextDecoder = jest.fn(() => ({ decode })); - - expect(toUtf8(new Uint8Array(0))).toBe(expected); - }); -}); diff --git a/packages/util-utf8/src/toUtf8.browser.ts b/packages/util-utf8/src/toUtf8.browser.ts deleted file mode 100644 index 872cd66bf87b..000000000000 --- a/packages/util-utf8/src/toUtf8.browser.ts +++ /dev/null @@ -1 +0,0 @@ -export const toUtf8 = (input: Uint8Array): string => new TextDecoder("utf-8").decode(input); diff --git a/packages/util-utf8/src/toUtf8.spec.ts b/packages/util-utf8/src/toUtf8.spec.ts deleted file mode 100644 index abc6c2901394..000000000000 --- a/packages/util-utf8/src/toUtf8.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { toUtf8 } from "./toUtf8"; - -const utf8StringsToByteArrays: Record = { - ABC: new Uint8Array(["A".charCodeAt(0), "B".charCodeAt(0), "C".charCodeAt(0)]), - "🐎👱❤": new Uint8Array([240, 159, 144, 142, 240, 159, 145, 177, 226, 157, 164]), - "☃💩": new Uint8Array([226, 152, 131, 240, 159, 146, 169]), - "The rain in Spain falls mainly on the plain.": new Uint8Array([ - 84, 104, 101, 32, 114, 97, 105, 110, 32, 105, 110, 32, 83, 112, 97, 105, 110, 32, 102, 97, 108, 108, 115, 32, 109, - 97, 105, 110, 108, 121, 32, 111, 110, 32, 116, 104, 101, 32, 112, 108, 97, 105, 110, 46, - ]), - "دست‌نوشته‌ها نمی‌سوزند": new Uint8Array([ - 216, 175, 216, 179, 216, 170, 226, 128, 140, 217, 134, 217, 136, 216, 180, 216, 170, 217, 135, 226, 128, 140, 217, - 135, 216, 167, 32, 217, 134, 217, 133, 219, 140, 226, 128, 140, 216, 179, 217, 136, 216, 178, 217, 134, 216, 175, - ]), - "Рукописи не горят": new Uint8Array([ - 208, 160, 209, 131, 208, 186, 208, 190, 208, 191, 208, 184, 209, 129, 208, 184, 32, 208, 189, 208, 181, 32, 208, - 179, 208, 190, 209, 128, 209, 143, 209, 130, - ]), -}; - -describe("toUtf8", () => { - for (const string of Object.keys(utf8StringsToByteArrays)) { - it(`should derive "${string}" from the UTF-8 decoded bytes`, () => { - expect(toUtf8(utf8StringsToByteArrays[string])).toBe(string); - }); - } - - it("should throw when given a number", () => { - expect(() => toUtf8(255 as any)).toThrow(); - }); -}); diff --git a/packages/util-utf8/src/toUtf8.ts b/packages/util-utf8/src/toUtf8.ts deleted file mode 100644 index 338cf21786bc..000000000000 --- a/packages/util-utf8/src/toUtf8.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { fromArrayBuffer } from "@aws-sdk/util-buffer-from"; - -export const toUtf8 = (input: Uint8Array): string => - fromArrayBuffer(input.buffer, input.byteOffset, input.byteLength).toString("utf8"); diff --git a/packages/util-waiter/.eslintrc.json b/packages/util-waiter/.eslintrc.json deleted file mode 100644 index a4ae1380eb4d..000000000000 --- a/packages/util-waiter/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "plugins": ["eslint-plugin-tsdoc"], - "rules": { - "tsdoc/syntax": "warn" - } -} diff --git a/packages/util-waiter/jest.config.js b/packages/util-waiter/jest.config.js deleted file mode 100644 index a8d1c2e49912..000000000000 --- a/packages/util-waiter/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const base = require("../../jest.config.base.js"); - -module.exports = { - ...base, -}; diff --git a/packages/util-waiter/package.json b/packages/util-waiter/package.json index 75bd8a06029e..697e65f186d1 100644 --- a/packages/util-waiter/package.json +++ b/packages/util-waiter/package.json @@ -3,8 +3,7 @@ "version": "3.370.0", "description": "Shared utilities for client waiters for the AWS SDK", "dependencies": { - "@aws-sdk/abort-controller": "*", - "@aws-sdk/types": "*", + "@smithy/util-waiter": "^1.0.1", "tslib": "^2.5.0" }, "scripts": { @@ -15,7 +14,7 @@ "build:types": "tsc -p tsconfig.types.json", "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", - "test": "jest" + "test": "exit 0" }, "author": { "name": "AWS SDK for JavaScript Team", diff --git a/packages/util-waiter/src/createWaiter.spec.ts b/packages/util-waiter/src/createWaiter.spec.ts deleted file mode 100644 index cd087da67d4e..000000000000 --- a/packages/util-waiter/src/createWaiter.spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { AbortController } from "@aws-sdk/abort-controller"; - -import { WaiterOptions, WaiterState } from "./waiter"; - -const mockValidate = jest.fn(); -jest.mock("./utils/validate", () => ({ - validateWaiterOptions: mockValidate, -})); - -import { createWaiter } from "./createWaiter"; - -describe("createWaiter", () => { - beforeEach(() => { - jest.useFakeTimers({ legacyFakeTimers: true }); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - const minimalWaiterConfig = { - minDelay: 2, - maxDelay: 120, - maxWaitTime: 9999, - client: "client", - } as WaiterOptions; - const input = "input"; - - const abortedState = { - state: WaiterState.ABORTED, - }; - const failureState = { - state: WaiterState.FAILURE, - }; - const retryState = { - state: WaiterState.RETRY, - }; - const successState = { - state: WaiterState.SUCCESS, - }; - - it("should abort when abortController is signalled", async () => { - const abortController = new AbortController(); - const mockAcceptorChecks = jest.fn().mockResolvedValue(retryState); - const statusPromise = createWaiter( - { - ...minimalWaiterConfig, - maxWaitTime: 20, - abortController, - }, - input, - mockAcceptorChecks - ); - jest.advanceTimersByTime(10 * 1000); - abortController.abort(); // Abort before maxWaitTime(20s); - expect(await statusPromise).toEqual(abortedState); - }); - - it("should success when acceptor checker returns seccess", async () => { - const mockAcceptorChecks = jest.fn().mockResolvedValue(successState); - const statusPromise = createWaiter( - { - ...minimalWaiterConfig, - maxWaitTime: 20, - }, - input, - mockAcceptorChecks - ); - jest.advanceTimersByTime(minimalWaiterConfig.minDelay * 1000); - expect(await statusPromise).toEqual(successState); - }); - - it("should fail when acceptor checker returns failure", async () => { - const mockAcceptorChecks = jest.fn().mockResolvedValue(failureState); - const statusPromise = createWaiter( - { - ...minimalWaiterConfig, - maxWaitTime: 20, - }, - input, - mockAcceptorChecks - ); - jest.advanceTimersByTime(minimalWaiterConfig.minDelay * 1000); - expect(await statusPromise).toEqual(failureState); - }); -}); diff --git a/packages/util-waiter/src/createWaiter.ts b/packages/util-waiter/src/createWaiter.ts deleted file mode 100644 index 33f1c6b62095..000000000000 --- a/packages/util-waiter/src/createWaiter.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { AbortSignal } from "@aws-sdk/types"; - -import { runPolling } from "./poller"; -import { validateWaiterOptions } from "./utils"; -import { WaiterOptions, WaiterResult, waiterServiceDefaults, WaiterState } from "./waiter"; - -const abortTimeout = async (abortSignal: AbortSignal): Promise => { - return new Promise((resolve) => { - abortSignal.onabort = () => resolve({ state: WaiterState.ABORTED }); - }); -}; - -/** - * Create a waiter promise that only resolves when: - * 1. Abort controller is signaled - * 2. Max wait time is reached - * 3. `acceptorChecks` succeeds, or fails - * Otherwise, it invokes `acceptorChecks` with exponential-backoff delay. - * - * @internal - */ -export const createWaiter = async ( - options: WaiterOptions, - input: Input, - acceptorChecks: (client: Client, input: Input) => Promise -): Promise => { - const params = { - ...waiterServiceDefaults, - ...options, - }; - validateWaiterOptions(params); - - const exitConditions = [runPolling(params, input, acceptorChecks)]; - if (options.abortController) { - exitConditions.push(abortTimeout(options.abortController.signal)); - } - - if (options.abortSignal) { - exitConditions.push(abortTimeout(options.abortSignal)); - } - - return Promise.race(exitConditions); -}; diff --git a/packages/util-waiter/src/index.spec.ts b/packages/util-waiter/src/index.spec.ts deleted file mode 100644 index 3bf2ab465c49..000000000000 --- a/packages/util-waiter/src/index.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as exported from "./index"; - -describe("Waiter util module exports", () => { - it("should export the proper functions", () => { - expect(exported.createWaiter).toBeDefined(); - }); -}); diff --git a/packages/util-waiter/src/index.ts b/packages/util-waiter/src/index.ts index d77f139a4ce1..524189f183e4 100644 --- a/packages/util-waiter/src/index.ts +++ b/packages/util-waiter/src/index.ts @@ -1,2 +1 @@ -export * from "./createWaiter"; -export * from "./waiter"; +export * from "@smithy/util-waiter"; diff --git a/packages/util-waiter/src/poller.spec.ts b/packages/util-waiter/src/poller.spec.ts deleted file mode 100644 index 9b2c292f53ae..000000000000 --- a/packages/util-waiter/src/poller.spec.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { AbortController } from "@aws-sdk/abort-controller"; - -import { runPolling } from "./poller"; -import { sleep } from "./utils/sleep"; -import { WaiterOptions, WaiterState } from "./waiter"; - -jest.mock("./utils/sleep"); - -describe(runPolling.name, () => { - const config = { - minDelay: 2, - maxDelay: 30, - maxWaitTime: 99999, - client: "mockClient", - } as WaiterOptions; - const input = "mockInput"; - const abortedState = { - state: WaiterState.ABORTED, - }; - const failureState = { - state: WaiterState.FAILURE, - reason: { - mockedReason: "some-failure-value", - }, - }; - const successState = { - state: WaiterState.SUCCESS, - reason: { - mockedReason: "some-success-value", - }, - }; - const retryState = { - state: WaiterState.RETRY, - reason: undefined, - }; - const timeoutState = { - state: WaiterState.TIMEOUT, - }; - - let mockAcceptorChecks; - - beforeEach(() => { - (sleep as jest.Mock).mockResolvedValueOnce(""); - jest.spyOn(global.Math, "random").mockReturnValue(0.5); - }); - - afterEach(() => { - jest.clearAllMocks(); - jest.spyOn(global.Math, "random").mockRestore(); - }); - - it("should returns state and reason in case of failure", async () => { - mockAcceptorChecks = jest.fn().mockResolvedValueOnce(failureState); - await expect(runPolling(config, input, mockAcceptorChecks)).resolves.toStrictEqual(failureState); - - expect(mockAcceptorChecks).toHaveBeenCalled(); - expect(mockAcceptorChecks).toHaveBeenCalledTimes(1); - expect(mockAcceptorChecks).toHaveBeenCalledWith(config.client, input); - expect(sleep).toHaveBeenCalledTimes(0); - }); - - it("returns state and reason in case of success", async () => { - mockAcceptorChecks = jest.fn().mockResolvedValueOnce(successState); - await expect(runPolling(config, input, mockAcceptorChecks)).resolves.toStrictEqual(successState); - expect(mockAcceptorChecks).toHaveBeenCalled(); - expect(mockAcceptorChecks).toHaveBeenCalledTimes(1); - expect(mockAcceptorChecks).toHaveBeenCalledWith(config.client, input); - expect(sleep).toHaveBeenCalledTimes(0); - }); - - it("sleeps as per exponentialBackoff in case of retry", async () => { - mockAcceptorChecks = jest - .fn() - .mockResolvedValueOnce(retryState) - .mockResolvedValueOnce(retryState) - .mockResolvedValueOnce(retryState) - .mockResolvedValueOnce(retryState) - .mockResolvedValueOnce(retryState) - .mockResolvedValueOnce(retryState) - .mockResolvedValueOnce(retryState) - .mockResolvedValueOnce(successState); - - await expect(runPolling(config, input, mockAcceptorChecks)).resolves.toStrictEqual(successState); - - expect(sleep).toHaveBeenCalled(); - expect(mockAcceptorChecks).toHaveBeenCalledTimes(8); - expect(sleep).toHaveBeenCalledTimes(7); - expect(sleep).toHaveBeenNthCalledWith(1, 2); // min delay. random(2, 2) - expect(sleep).toHaveBeenNthCalledWith(2, 3); // random(2, 4) - expect(sleep).toHaveBeenNthCalledWith(3, 5); // +random(2, 8) - expect(sleep).toHaveBeenNthCalledWith(4, 9); // +random(2, 16) - expect(sleep).toHaveBeenNthCalledWith(5, 30); // max delay - expect(sleep).toHaveBeenNthCalledWith(6, 30); // max delay - expect(sleep).toHaveBeenNthCalledWith(7, 30); // max delay - }); - - it("resolves after the last attempt before reaching maxWaitTime ", async () => { - let now = Date.now(); - const delay = 2; - const nowMock = jest - .spyOn(Date, "now") - .mockReturnValueOnce(now) // 1st invoke for getting the time stamp to wait until - .mockImplementation(() => { - const rtn = now; - now += delay * 1000; - return rtn; - }); - const localConfig = { - ...config, - minDelay: delay, - maxDelay: delay, - maxWaitTime: 5, - }; - - mockAcceptorChecks = jest.fn().mockResolvedValue(retryState); - await expect(runPolling(localConfig, input, mockAcceptorChecks)).resolves.toStrictEqual(timeoutState); - expect(sleep).toHaveBeenCalled(); - expect(sleep).toHaveBeenCalledTimes(2); - nowMock.mockReset(); - }); - - it("resolves when abortController is signalled", async () => { - const abortController = new AbortController(); - const localConfig = { - ...config, - abortController, - }; - - mockAcceptorChecks = jest.fn().mockResolvedValue(retryState); - abortController.abort(); - await expect(runPolling(localConfig, input, mockAcceptorChecks)).resolves.toStrictEqual(abortedState); - expect(sleep).not.toHaveBeenCalled(); - }); -}); diff --git a/packages/util-waiter/src/poller.ts b/packages/util-waiter/src/poller.ts deleted file mode 100644 index 691666ad38d7..000000000000 --- a/packages/util-waiter/src/poller.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { sleep } from "./utils/sleep"; -import { WaiterOptions, WaiterResult, WaiterState } from "./waiter"; - -/** - * @internal - * - * Reference: https://smithy.io/2.0/additional-specs/waiters.html#waiter-retries - */ -const exponentialBackoffWithJitter = (minDelay: number, maxDelay: number, attemptCeiling: number, attempt: number) => { - if (attempt > attemptCeiling) return maxDelay; - const delay = minDelay * 2 ** (attempt - 1); - return randomInRange(minDelay, delay); -}; - -const randomInRange = (min: number, max: number) => min + Math.random() * (max - min); - -/** - * Function that runs polling as part of waiters. This will make one inital attempt and then - * subsequent attempts with an increasing delay. - * @param params - options passed to the waiter. - * @param client - AWS SDK Client - * @param input - client input - * @param stateChecker - function that checks the acceptor states on each poll. - */ -export const runPolling = async ( - { minDelay, maxDelay, maxWaitTime, abortController, client, abortSignal }: WaiterOptions, - input: Input, - acceptorChecks: (client: Client, input: Input) => Promise -): Promise => { - const { state, reason } = await acceptorChecks(client, input); - if (state !== WaiterState.RETRY) { - return { state, reason }; - } - - let currentAttempt = 1; - const waitUntil = Date.now() + maxWaitTime * 1000; - // The max attempt number that the derived delay time tend to increase. - // Pre-compute this number to avoid Number type overflow. - const attemptCeiling = Math.log(maxDelay / minDelay) / Math.log(2) + 1; - while (true) { - if (abortController?.signal?.aborted || abortSignal?.aborted) { - return { state: WaiterState.ABORTED }; - } - const delay = exponentialBackoffWithJitter(minDelay, maxDelay, attemptCeiling, currentAttempt); - // Resolve the promise explicitly at timeout or aborted. Otherwise this while loop will keep making API call until - // `acceptorCheck` returns non-retry status, even with the Promise.race() outside. - if (Date.now() + delay * 1000 > waitUntil) { - return { state: WaiterState.TIMEOUT }; - } - await sleep(delay); - const { state, reason } = await acceptorChecks(client, input); - if (state !== WaiterState.RETRY) { - return { state, reason }; - } - - currentAttempt += 1; - } -}; diff --git a/packages/util-waiter/src/utils/index.ts b/packages/util-waiter/src/utils/index.ts deleted file mode 100644 index b9a320520ca4..000000000000 --- a/packages/util-waiter/src/utils/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @internal - */ -export * from "./sleep"; -/** - * @internal - */ -export * from "./validate"; diff --git a/packages/util-waiter/src/utils/sleep.spec.ts b/packages/util-waiter/src/utils/sleep.spec.ts deleted file mode 100644 index 151aa1973a6f..000000000000 --- a/packages/util-waiter/src/utils/sleep.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { sleep } from "./sleep"; - -describe("Sleep Module", () => { - beforeEach(() => { - jest.useFakeTimers({ legacyFakeTimers: true }); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - describe(sleep.name, () => { - it("should call setTimeout with the proper number of milliseconds", () => { - sleep(1); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000); - }); - }); -}); diff --git a/packages/util-waiter/src/utils/sleep.ts b/packages/util-waiter/src/utils/sleep.ts deleted file mode 100644 index 66062b93abb7..000000000000 --- a/packages/util-waiter/src/utils/sleep.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @internal - */ -export const sleep = (seconds: number) => { - return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); -}; diff --git a/packages/util-waiter/src/utils/validate.spec.ts b/packages/util-waiter/src/utils/validate.spec.ts deleted file mode 100644 index bff31c85656b..000000000000 --- a/packages/util-waiter/src/utils/validate.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { WaiterOptions } from "../waiter"; -import { validateWaiterOptions } from "./validate"; - -describe(validateWaiterOptions.name, () => { - let waiterOptions: WaiterOptions; - - beforeEach(() => { - waiterOptions = { - maxWaitTime: 120, - minDelay: 20, - maxDelay: 1200, - client: "client", - }; - }); - - it("should not throw an error when maxDelay is proper", (done) => { - waiterOptions.maxDelay = 300; - waiterOptions.minDelay = 200; - waiterOptions.maxWaitTime = 250; - try { - validateWaiterOptions(waiterOptions); - expect(1).toBe(1); - done(); - } catch (e) { - expect(e).toBe("SHOULD NOT ERROR HERE"); - } - }); - - it("should not throw an error when maxDelay is less than minDelay", (done) => { - waiterOptions.maxDelay = 120; - waiterOptions.minDelay = 200; - waiterOptions.maxWaitTime = 250; - try { - validateWaiterOptions(waiterOptions); - expect(1).toBe("SHOULD NOT GET HERE"); - } catch (e) { - expect(e.toString()).toBe( - "Error: WaiterConfiguration.maxDelay [120] must be greater than WaiterConfiguration.minDelay [200] for this waiter" - ); - done(); - } - }); - - it("should not throw an error when maxWaitTime is proper", (done) => { - waiterOptions.maxWaitTime = 300; - waiterOptions.minDelay = 200; - try { - validateWaiterOptions(waiterOptions); - expect(1).toBe(1); - done(); - } catch (e) { - expect(e).toBe("SHOULD NOT ERROR HERE"); - } - }); - - it("should throw when maxWaitTime is less than 0", (done) => { - waiterOptions.maxWaitTime = -2; - waiterOptions.minDelay = -1; - try { - validateWaiterOptions(waiterOptions); - } catch (e) { - expect(e.toString()).toBe("Error: WaiterConfiguration.maxWaitTime must be greater than 0"); - done(); - } - }); - - it("should throw when maxWaitTime is less than minDelay", (done) => { - waiterOptions.maxWaitTime = 150; - waiterOptions.minDelay = 200; - try { - validateWaiterOptions(waiterOptions); - } catch (e) { - expect(e.toString()).toBe( - "Error: WaiterConfiguration.maxWaitTime [150] must be greater than WaiterConfiguration.minDelay [200] for this waiter" - ); - done(); - } - }); - - it("should throw when maxWaitTime is equal tominDelay", () => { - waiterOptions.maxWaitTime = 200; - waiterOptions.minDelay = 200; - try { - validateWaiterOptions(waiterOptions); - } catch (e) { - expect(e.toString()).toBe( - "Error: WaiterConfiguration.maxWaitTime [200] must be greater than WaiterConfiguration.minDelay [200] for this waiter" - ); - } - }); -}); diff --git a/packages/util-waiter/src/utils/validate.ts b/packages/util-waiter/src/utils/validate.ts deleted file mode 100644 index 193f38c89ca0..000000000000 --- a/packages/util-waiter/src/utils/validate.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { WaiterOptions } from "../waiter"; - -/** - * @internal - * - * Validates that waiter options are passed correctly - * @param options - a waiter configuration object - */ -export const validateWaiterOptions = (options: WaiterOptions): void => { - if (options.maxWaitTime < 1) { - throw new Error(`WaiterConfiguration.maxWaitTime must be greater than 0`); - } else if (options.minDelay < 1) { - throw new Error(`WaiterConfiguration.minDelay must be greater than 0`); - } else if (options.maxDelay < 1) { - throw new Error(`WaiterConfiguration.maxDelay must be greater than 0`); - } else if (options.maxWaitTime <= options.minDelay) { - throw new Error( - `WaiterConfiguration.maxWaitTime [${options.maxWaitTime}] must be greater than WaiterConfiguration.minDelay [${options.minDelay}] for this waiter` - ); - } else if (options.maxDelay < options.minDelay) { - throw new Error( - `WaiterConfiguration.maxDelay [${options.maxDelay}] must be greater than WaiterConfiguration.minDelay [${options.minDelay}] for this waiter` - ); - } -}; diff --git a/packages/util-waiter/src/waiter.ts b/packages/util-waiter/src/waiter.ts deleted file mode 100644 index 5bd89f57032d..000000000000 --- a/packages/util-waiter/src/waiter.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { WaiterConfiguration as WaiterConfiguration__ } from "@aws-sdk/types"; - -/** - * @internal - */ -export interface WaiterConfiguration extends WaiterConfiguration__ {} - -/** - * @internal - */ -export const waiterServiceDefaults = { - minDelay: 2, - maxDelay: 120, -}; - -/** - * @internal - */ -export type WaiterOptions = WaiterConfiguration & - Required, "minDelay" | "maxDelay">>; - -/** - * @internal - */ -export enum WaiterState { - ABORTED = "ABORTED", - FAILURE = "FAILURE", - SUCCESS = "SUCCESS", - RETRY = "RETRY", - TIMEOUT = "TIMEOUT", -} - -/** - * @internal - */ -export type WaiterResult = { - state: WaiterState; - - /** - * (optional) Indicates a reason for why a waiter has reached its state. - */ - reason?: any; -}; - -/** - * @internal - * - * Handles and throws exceptions resulting from the waiterResult - * @param result - WaiterResult - */ -export const checkExceptions = (result: WaiterResult): WaiterResult => { - if (result.state === WaiterState.ABORTED) { - const abortError = new Error( - `${JSON.stringify({ - ...result, - reason: "Request was aborted", - })}` - ); - abortError.name = "AbortError"; - throw abortError; - } else if (result.state === WaiterState.TIMEOUT) { - const timeoutError = new Error( - `${JSON.stringify({ - ...result, - reason: "Waiter has timed out", - })}` - ); - timeoutError.name = "TimeoutError"; - throw timeoutError; - } else if (result.state !== WaiterState.SUCCESS) { - throw new Error(`${JSON.stringify({ result })}`); - } - return result; -}; diff --git a/private/aws-middleware-test/package.json b/private/aws-middleware-test/package.json index 4820ed5cd4a5..0648e5dbf822 100644 --- a/private/aws-middleware-test/package.json +++ b/private/aws-middleware-test/package.json @@ -25,6 +25,10 @@ "@aws-sdk/client-sagemaker": "*", "@aws-sdk/client-sagemaker-runtime": "*", "@aws-sdk/client-xray": "*", + "@smithy/protocol-http": "^1.1.0", + "@smithy/types": "^1.1.0", + "@smithy/util-stream": "^1.0.1", + "@smithy/util-utf8": "^1.0.1", "tslib": "^2.5.0" }, "devDependencies": { diff --git a/packages/util-stream/src/util-stream.integ.spec.ts b/private/aws-middleware-test/src/util-stream.spec.ts similarity index 92% rename from packages/util-stream/src/util-stream.integ.spec.ts rename to private/aws-middleware-test/src/util-stream.spec.ts index 7b1622aa94a0..7427dc466bb0 100644 --- a/packages/util-stream/src/util-stream.integ.spec.ts +++ b/private/aws-middleware-test/src/util-stream.spec.ts @@ -1,11 +1,11 @@ import { Lambda } from "@aws-sdk/client-lambda"; -import { HttpResponse } from "@aws-sdk/protocol-http"; -import { HttpRequest as IHttpRequest } from "@aws-sdk/types"; -import { Uint8ArrayBlobAdapter } from "@aws-sdk/util-stream"; -import { fromUtf8 } from "@aws-sdk/util-utf8"; +import { HttpResponse } from "@smithy/protocol-http"; +import { HttpRequest as IHttpRequest } from "@smithy/types"; +import { Uint8ArrayBlobAdapter } from "@smithy/util-stream"; +import { fromUtf8 } from "@smithy/util-utf8"; import { Readable } from "stream"; -import { requireRequestsFrom } from "../../../private/aws-util-test/src"; +import { requireRequestsFrom } from "../../aws-util-test/src"; describe("util-stream", () => { describe(Lambda.name, () => { diff --git a/yarn.lock b/yarn.lock index 4465bf7aaa3a..a74f8b6e4eda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -163,19 +163,19 @@ integrity sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg== "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.7.5": - version "7.22.8" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.8.tgz#386470abe884302db9c82e8e5e87be9e46c86785" - integrity sha512-75+KxFB4CZqYRXjx4NlR4J7yGvKumBuZTmV4NV6v09dVXXkuYVYLT68N6HCzLvfJ+fWCxQsntNzKwwIXL4bHnw== + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.6.tgz#aafafbe86e9a1679d876b99dc46382964ef72494" + integrity sha512-HPIyDa6n+HKw5dEuway3vVAhBboYCtREBMp+IWeseZy6TFtzn6MHkCH2KKYUOC/vKKwgSMHQW4htBOrmuRPXfw== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.7" + "@babel/generator" "^7.22.5" "@babel/helper-compilation-targets" "^7.22.6" "@babel/helper-module-transforms" "^7.22.5" "@babel/helpers" "^7.22.6" - "@babel/parser" "^7.22.7" + "@babel/parser" "^7.22.6" "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.8" + "@babel/traverse" "^7.22.6" "@babel/types" "^7.22.5" "@nicolo-ribaudo/semver-v6" "^6.3.3" convert-source-map "^1.7.0" @@ -183,7 +183,7 @@ gensync "^1.0.0-beta.2" json5 "^2.2.2" -"@babel/generator@^7.22.7", "@babel/generator@^7.7.2": +"@babel/generator@^7.22.5", "@babel/generator@^7.22.7", "@babel/generator@^7.7.2": version "7.22.7" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.7.tgz#a6b8152d5a621893f2c9dacf9a4e286d520633d5" integrity sha512-p+jPjMG+SI8yvIaxGgeW24u7q9+5+TGpZh8/CuB7RhBKd7RCy8FayNEFNNKrNK/eUcY/4ExQqLmyrvBXKsIcwQ== @@ -297,10 +297,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5", "@babel/parser@^7.22.7": - version "7.22.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" - integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5", "@babel/parser@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.6.tgz#201f8b47be20c76c7c5743b9c16129760bf9a975" + integrity sha512-EIQu22vNkceq3LbjAq7knDf/UmtI2qbcNI8GRBlijez6TpQLvSodJPYfydQmNA5buwkxxxa/PVI44jjYZ+/cLw== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -416,18 +416,18 @@ "@babel/parser" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/traverse@^7.22.5", "@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8": - version "7.22.8" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" - integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== +"@babel/traverse@^7.22.5", "@babel/traverse@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.6.tgz#8f2f83a5c588251584914debeee38f35f661a300" + integrity sha512-53CijMvKlLIDlOTrdWiHileRddlIiwUIyCKqYa7lYnnPldXCG5dUSN38uT0cA6i7rHWNKJLH0VU/Kxdr1GzB3w== dependencies: "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.7" + "@babel/generator" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-function-name" "^7.22.5" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.7" + "@babel/parser" "^7.22.6" "@babel/types" "^7.22.5" debug "^4.1.0" globals "^11.1.0" @@ -899,28 +899,28 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.6.1.tgz#b48ba7b9c34b51483e6d590f46e5837f1ab5f639" - integrity sha512-Aj772AYgwTSr5w8qnyoJ0eDYvN6bMsH3ORH1ivMotrInHLKdUz6BDlaEXHdM6kODaBIkNIyQGzsMvRdOv7VG7Q== +"@jest/console@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.6.0.tgz#ad0ae19e56e3ca34f620bab7b3e0bb7e3e655275" + integrity sha512-anb6L1yg7uPQpytNVA5skRaXy3BmrsU8icRhTVNbWdjYWDDfy8M1Kq5HIVRpYoABdbpqsc5Dr+jtu4+qWRQBiQ== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.0" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.6.1" - jest-util "^29.6.1" + jest-message-util "^29.6.0" + jest-util "^29.6.0" slash "^3.0.0" -"@jest/core@^29.5.0", "@jest/core@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.1.tgz#fac0d9ddf320490c93356ba201451825231e95f6" - integrity sha512-CcowHypRSm5oYQ1obz1wfvkjZZ2qoQlrKKvlfPwh5jUXVU12TWr2qMeH8chLMuTFzHh5a1g2yaqlqDICbr+ukQ== - dependencies: - "@jest/console" "^29.6.1" - "@jest/reporters" "^29.6.1" - "@jest/test-result" "^29.6.1" - "@jest/transform" "^29.6.1" - "@jest/types" "^29.6.1" +"@jest/core@^29.5.0", "@jest/core@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.0.tgz#a71da7b99777ff4a3d534bd2529358872909905f" + integrity sha512-5dbMHfY/5R9m8NbgmB3JlxQqooZ/ooPSOiwEQZZ+HODwJTbIu37seVcZNBK29aMdXtjvTRB3f6LCvkKq+r8uQA== + dependencies: + "@jest/console" "^29.6.0" + "@jest/reporters" "^29.6.0" + "@jest/test-result" "^29.6.0" + "@jest/transform" "^29.6.0" + "@jest/types" "^29.6.0" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" @@ -928,80 +928,80 @@ exit "^0.1.2" graceful-fs "^4.2.9" jest-changed-files "^29.5.0" - jest-config "^29.6.1" - jest-haste-map "^29.6.1" - jest-message-util "^29.6.1" + jest-config "^29.6.0" + jest-haste-map "^29.6.0" + jest-message-util "^29.6.0" jest-regex-util "^29.4.3" - jest-resolve "^29.6.1" - jest-resolve-dependencies "^29.6.1" - jest-runner "^29.6.1" - jest-runtime "^29.6.1" - jest-snapshot "^29.6.1" - jest-util "^29.6.1" - jest-validate "^29.6.1" - jest-watcher "^29.6.1" + jest-resolve "^29.6.0" + jest-resolve-dependencies "^29.6.0" + jest-runner "^29.6.0" + jest-runtime "^29.6.0" + jest-snapshot "^29.6.0" + jest-util "^29.6.0" + jest-validate "^29.6.0" + jest-watcher "^29.6.0" micromatch "^4.0.4" - pretty-format "^29.6.1" + pretty-format "^29.6.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.5.0", "@jest/environment@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.1.tgz#ee358fff2f68168394b4a50f18c68278a21fe82f" - integrity sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A== +"@jest/environment@^29.5.0", "@jest/environment@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.0.tgz#a873d228159cbba812505f7d13e2d1a2d04a577a" + integrity sha512-bUZLYUxYlUIsslBbxII0fq0kr1+friI3Gty+cRLmocGB1jdcAHs7FS8QdCDqedE8q4DZE1g/AJHH6OJZBLGGsg== dependencies: - "@jest/fake-timers" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/fake-timers" "^29.6.0" + "@jest/types" "^29.6.0" "@types/node" "*" - jest-mock "^29.6.1" + jest-mock "^29.6.0" -"@jest/expect-utils@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.1.tgz#ab83b27a15cdd203fe5f68230ea22767d5c3acc5" - integrity sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw== +"@jest/expect-utils@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.0.tgz#14596ba728d61b0cf70f7d5c8fb88b8a82ea9def" + integrity sha512-LLSQQN7oypMSETKoPWpsWYVKJd9LQWmSDDAc4hUQ4JocVC7LAMy9R3ZMhlnLwbcFvQORZnZR7HM893Px6cJhvA== dependencies: jest-get-type "^29.4.3" -"@jest/expect@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.1.tgz#fef18265188f6a97601f1ea0a2912d81a85b4657" - integrity sha512-N5xlPrAYaRNyFgVf2s9Uyyvr795jnB6rObuPx4QFvNJz8aAjpZUDfO4bh5G/xuplMID8PrnuF1+SfSyDxhsgYg== +"@jest/expect@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.0.tgz#2a25759ec696bc03d3e5cfeba5a26732431f844f" + integrity sha512-a7pISPW28Q3c0/pLwz4mQ6tbAI+hc8/0CJp9ix6e9U4dQ6TiHQX82CT5DV5BMWaw8bFH4E6zsfZxXdn6Ka23Bw== dependencies: - expect "^29.6.1" - jest-snapshot "^29.6.1" + expect "^29.6.0" + jest-snapshot "^29.6.0" -"@jest/fake-timers@^29.5.0", "@jest/fake-timers@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.1.tgz#c773efddbc61e1d2efcccac008139f621de57c69" - integrity sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg== +"@jest/fake-timers@^29.5.0", "@jest/fake-timers@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.0.tgz#9751cbabc86a39a1e6827cfcbabeba0207a63c97" + integrity sha512-nuCU46AsZoskthWSDS2Aj6LARgyNcp5Fjx2qxsO/fPl1Wp1CJ+dBDqs0OkEcJK8FBeV/MbjH5efe79M2sHcV+A== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.0" "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^29.6.1" - jest-mock "^29.6.1" - jest-util "^29.6.1" + jest-message-util "^29.6.0" + jest-mock "^29.6.0" + jest-util "^29.6.0" -"@jest/globals@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.1.tgz#c8a8923e05efd757308082cc22893d82b8aa138f" - integrity sha512-2VjpaGy78JY9n9370H8zGRCFbYVWwjY6RdDMhoJHa1sYfwe6XM/azGN0SjY8kk7BOZApIejQ1BFPyH7FPG0w3A== +"@jest/globals@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.0.tgz#e1603da83f69ed1a75e272d15da34a6a2fca1e24" + integrity sha512-IQQ3hZ2D/hwEwXSMv5GbfhzdH0nTQR3KPYxnuW6gYWbd6+7/zgMz7Okn6EgBbNtJNONq03k5EKA6HqGyzRbpeg== dependencies: - "@jest/environment" "^29.6.1" - "@jest/expect" "^29.6.1" - "@jest/types" "^29.6.1" - jest-mock "^29.6.1" + "@jest/environment" "^29.6.0" + "@jest/expect" "^29.6.0" + "@jest/types" "^29.6.0" + jest-mock "^29.6.0" -"@jest/reporters@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.1.tgz#3325a89c9ead3cf97ad93df3a427549d16179863" - integrity sha512-9zuaI9QKr9JnoZtFQlw4GREQbxgmNYXU6QuWtmuODvk5nvPUeBYapVR/VYMyi2WSx3jXTLJTJji8rN6+Cm4+FA== +"@jest/reporters@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.0.tgz#09e6d47b3d9b69172cbc344d4cb8954966a7a466" + integrity sha512-dWEq4HI0VvHcAD6XTtyBKKARLytyyWPIy1SvGOcU91106MfvHPdxZgupFwVHd8TFpZPpA3SebYjtwS5BUS76Rw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.6.1" - "@jest/test-result" "^29.6.1" - "@jest/transform" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/console" "^29.6.0" + "@jest/test-result" "^29.6.0" + "@jest/transform" "^29.6.0" + "@jest/types" "^29.6.0" "@jridgewell/trace-mapping" "^0.3.18" "@types/node" "*" chalk "^4.0.0" @@ -1014,9 +1014,9 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.6.1" - jest-util "^29.6.1" - jest-worker "^29.6.1" + jest-message-util "^29.6.0" + jest-util "^29.6.0" + jest-worker "^29.6.0" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" @@ -1045,51 +1045,51 @@ callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.1.tgz#850e565a3f58ee8ca6ec424db00cb0f2d83c36ba" - integrity sha512-Ynr13ZRcpX6INak0TPUukU8GWRfm/vAytE3JbJNGAvINySWYdfE7dGZMbk36oVuK4CigpbhMn8eg1dixZ7ZJOw== +"@jest/test-result@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.0.tgz#03bd32d3bb696eff5affecf918468bc633fc32d5" + integrity sha512-9qLb7xITeyWhM4yatn2muqfomuoCTOhv0QV9i7XiIyYi3QLfnvPv5NeJp5u0PZeutAOROMLKakOkmoAisOr3YQ== dependencies: - "@jest/console" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/console" "^29.6.0" + "@jest/types" "^29.6.0" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.1.tgz#e3e582ee074dd24ea9687d7d1aaf05ee3a9b068e" - integrity sha512-oBkC36PCDf/wb6dWeQIhaviU0l5u6VCsXa119yqdUosYAt7/FbQU2M2UoziO3igj/HBDEgp57ONQ3fm0v9uyyg== +"@jest/test-sequencer@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.0.tgz#30a70e2dcc7dcf1e0f1170b97384883ce0a7d6e5" + integrity sha512-HYCS3LKRQotKWj2mnA3AN13PPevYZu8MJKm12lzYojpJNnn6kI/3PWmr1At/e3tUu+FHQDiOyaDVuR4EV3ezBw== dependencies: - "@jest/test-result" "^29.6.1" + "@jest/test-result" "^29.6.0" graceful-fs "^4.2.9" - jest-haste-map "^29.6.1" + jest-haste-map "^29.6.0" slash "^3.0.0" -"@jest/transform@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.1.tgz#acb5606019a197cb99beda3c05404b851f441c92" - integrity sha512-URnTneIU3ZjRSaf906cvf6Hpox3hIeJXRnz3VDSw5/X93gR8ycdfSIEy19FlVx8NFmpN7fe3Gb1xF+NjXaQLWg== +"@jest/transform@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.0.tgz#dcbb37e35412310073e633816fd7dbc11773596d" + integrity sha512-bhP/KxPo3e322FJ0nKAcb6WVK76ZYyQd1lWygJzoSqP8SYMSLdxHqP4wnPTI4WvbB8PKPDV30y5y7Tya4RHOBA== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.0" "@jridgewell/trace-mapping" "^0.3.18" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.6.1" + jest-haste-map "^29.6.0" jest-regex-util "^29.4.3" - jest-util "^29.6.1" + jest-util "^29.6.0" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@^29.5.0", "@jest/types@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2" - integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== +"@jest/types@^29.5.0", "@jest/types@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.0.tgz#717646103c5715394d78c011a08b3cbb83d738e8" + integrity sha512-8XCgL9JhqbJTFnMRjEAO+TuW251+MoMd5BSzLiE3vvzpQ8RlBxy8NoyNkDhs3K3OL3HeVinlOl9or5p7GTeOLg== dependencies: "@jest/schemas" "^29.6.0" "@types/istanbul-lib-coverage" "^2.0.0" @@ -2313,6 +2313,14 @@ "@smithy/types" "^1.1.1" tslib "^2.5.0" +"@smithy/chunked-blob-reader-native@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-1.0.1.tgz#789b2881e91901bf8735c598b9477e5067b81f64" + integrity sha512-Q0uc30PGyJYoG3+FIrIIzkGMaYPzSqw9JgaMoTvk5SU7+WrC4XcyEQHbfFkl/U3v8fyQhHZCxTLjSMJYNLAf/Q== + dependencies: + "@smithy/util-base64" "^1.0.1" + tslib "^2.5.0" + "@smithy/chunked-blob-reader-native@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-1.0.2.tgz#c6045b30f37824b4648ee8d06d68b2e8df400460" @@ -2321,14 +2329,21 @@ "@smithy/util-base64" "^1.0.2" tslib "^2.5.0" -"@smithy/chunked-blob-reader@^1.0.1", "@smithy/chunked-blob-reader@^1.0.2": +"@smithy/chunked-blob-reader@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader/-/chunked-blob-reader-1.0.1.tgz#382b49680bfb8d764c0af24d3164ce7047ea18f2" + integrity sha512-5cFQ9xLn0CbRxXhpWfnGMSGb9zdw2E8tvA9ACJ5arzyqDvE5gG9qluXgZvmr3BODp/P4ODXDjBjqSOm60eqcFg== + dependencies: + tslib "^2.5.0" + +"@smithy/chunked-blob-reader@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader/-/chunked-blob-reader-1.0.2.tgz#be1d2c91a4430de371207c5f143228d4cd671eb0" integrity sha512-B2x76NIPqC883lvnISprpO2eDlI41SznmoDTehoPbVpVcI2A7Nwg3nYA+p8XTpFF06cIFgjmOs9M0il2HquFQQ== dependencies: tslib "^2.5.0" -"@smithy/config-resolver@^1.0.1", "@smithy/config-resolver@^1.0.2": +"@smithy/config-resolver@^1.0.1": version "1.0.2" resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-1.0.2.tgz#d4f556a44292b41b5c067662a4bd5049dea40e35" integrity sha512-8Bk7CgnVKg1dn5TgnjwPz2ebhxeR7CjGs5yhVYH3S8x0q8yPZZVWwpRIglwXaf5AZBzJlNO1lh+lUhMf2e73zQ== @@ -2338,7 +2353,7 @@ "@smithy/util-middleware" "^1.0.2" tslib "^2.5.0" -"@smithy/credential-provider-imds@^1.0.1", "@smithy/credential-provider-imds@^1.0.2": +"@smithy/credential-provider-imds@^1.0.1": version "1.0.2" resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-1.0.2.tgz#7aa797c0d95448eb3dccb988b40e62db8989576f" integrity sha512-fLjCya+JOu2gPJpCiwSUyoLvT8JdNJmOaTOkKYBZoGf7CzqR6lluSyI+eboZnl/V0xqcfcqBG4tgqCISmWS3/w== @@ -2385,7 +2400,7 @@ "@smithy/types" "^1.1.1" tslib "^2.5.0" -"@smithy/eventstream-serde-universal@^1.0.2": +"@smithy/eventstream-serde-universal@^1.0.1", "@smithy/eventstream-serde-universal@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-1.0.2.tgz#66c1ccc639cb64049291200bcda476b26875fd8e" integrity sha512-cQ9bT0j0x49cp8TQ1yZSnn4+9qU0WQSTkoucl3jKRoTZMzNYHg62LQao6HTQ3Jgd77nAXo00c7hqUEjHXwNA+A== @@ -2469,39 +2484,39 @@ tslib "^2.5.0" "@smithy/middleware-content-length@^1.0.1": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-1.0.2.tgz#63099f8d01b3419b65e21cfd07b0c2ef47d1f473" - integrity sha512-pa1/SgGIrSmnEr2c9Apw7CdU4l/HW0fK3+LKFCPDYJrzM0JdYpqjQzgxi31P00eAkL0EFBccpus/p1n2GF9urw== + version "1.0.1" + resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-1.0.1.tgz#78747dfc7079e542c04675bb3ab1370c35511f7b" + integrity sha512-vWWigayk5i2cFp9xPX5vdzHyK+P0t/xZ3Ovp4Ss+c8JQ1Hlq2kpJZVWtTKsmdfND5rVo5lu0kD5wgAMUCcmuhw== dependencies: - "@smithy/protocol-http" "^1.1.1" - "@smithy/types" "^1.1.1" + "@smithy/protocol-http" "^1.1.0" + "@smithy/types" "^1.1.0" tslib "^2.5.0" "@smithy/middleware-endpoint@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-1.0.3.tgz#ff4b1c0a83eb8d8b8d3937f434a95efbbf43e1cd" - integrity sha512-GsWvTXMFjSgl617PCE2km//kIjjtvMRrR2GAuRDIS9sHiLwmkS46VWaVYy+XE7ubEsEtzZ5yK2e8TKDR6Qr5Lw== + version "1.0.2" + resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-1.0.2.tgz#6e8913e90bad7d73dc57f2a3dadfb0c4f045c9b5" + integrity sha512-F3CyXgjtDI4quGFkDmVNytt6KMwlzzeMxtopk6Edue4uKdKcMC1vUmoRS5xTbFzKDDp4XwpnEV7FshPaL3eCPw== dependencies: - "@smithy/middleware-serde" "^1.0.2" - "@smithy/types" "^1.1.1" - "@smithy/url-parser" "^1.0.2" - "@smithy/util-middleware" "^1.0.2" + "@smithy/middleware-serde" "^1.0.1" + "@smithy/types" "^1.1.0" + "@smithy/url-parser" "^1.0.1" + "@smithy/util-middleware" "^1.0.1" tslib "^2.5.0" "@smithy/middleware-retry@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-1.0.4.tgz#8e9de0713dac7f7af405477d46bd4525ca7b9ea8" - integrity sha512-G7uRXGFL8c3F7APnoIMTtNAHH8vT4F2qVnAWGAZaervjupaUQuRRHYBLYubK0dWzOZz86BtAXKieJ5p+Ni2Xpg== + version "1.0.3" + resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-1.0.3.tgz#95cac65da17a313c836c9f4a83aa348aad8625da" + integrity sha512-ZRsjG8adtxQ456FULPqPFmWtrW44Fq8IgdQvQB+rC2RSho3OUzS+TiEIwb5Zs6rf2IoewITKtfdtsUZcxXO0ng== dependencies: - "@smithy/protocol-http" "^1.1.1" - "@smithy/service-error-classification" "^1.0.3" - "@smithy/types" "^1.1.1" - "@smithy/util-middleware" "^1.0.2" - "@smithy/util-retry" "^1.0.4" + "@smithy/protocol-http" "^1.1.0" + "@smithy/service-error-classification" "^1.0.2" + "@smithy/types" "^1.1.0" + "@smithy/util-middleware" "^1.0.1" + "@smithy/util-retry" "^1.0.3" tslib "^2.5.0" uuid "^8.3.2" -"@smithy/middleware-serde@^1.0.1", "@smithy/middleware-serde@^1.0.2": +"@smithy/middleware-serde@^1.0.1": version "1.0.2" resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-1.0.2.tgz#87b3a0211602ae991d9b756893eb6bf2e3e5f711" integrity sha512-T4PcdMZF4xme6koUNfjmSZ1MLi7eoFeYCtodQNQpBNsS77TuJt1A6kt5kP/qxrTvfZHyFlj0AubACoaUqgzPeg== @@ -2577,6 +2592,11 @@ dependencies: tslib "^2.5.0" +"@smithy/service-error-classification@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-1.0.2.tgz#9145b66b7935fbbde43e53c82853fc96a448a552" + integrity sha512-Q5CCuzYL5FGo6Rr/O+lZxXHm2hrRgbmMn8MgyjqZUWZg20COg20DuNtIbho2iht6CoB7jOpmpBqhWizLlzUZgg== + "@smithy/service-error-classification@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-1.0.3.tgz#c620c1562610d3351985eb6dd04262ca2657ae67" @@ -2652,7 +2672,7 @@ dependencies: tslib "^2.5.0" -"@smithy/util-buffer-from@^1.0.2": +"@smithy/util-buffer-from@^1.0.1", "@smithy/util-buffer-from@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-1.0.2.tgz#27e19573d721962bd2443f23d4edadb8206b2cb5" integrity sha512-lHAYIyrBO9RANrPvccnPjU03MJnWZ66wWuC5GjWWQVfsmPwU6m00aakZkzHdUT6tGCkGacXSgArP5wgTgA+oCw== @@ -2678,15 +2698,15 @@ tslib "^2.5.0" "@smithy/util-defaults-mode-node@^1.0.1": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-1.0.2.tgz#b295fe2a18568c1e21a85b6557e2b769452b4d95" - integrity sha512-9/BN63rlIsFStvI+AvljMh873Xw6bbI6b19b+PVYXyycQ2DDQImWcjnzRlHW7eP65CCUNGQ6otDLNdBQCgMXqg== + version "1.0.1" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-1.0.1.tgz#5efe0bc575ec2253a4723d05a6f1fd28a567996e" + integrity sha512-XQM3KvqRLgv7bwAzVkXTITkOmcOINoG9icJiGT8FA0zV35lY5UvyIsg5kHw01xigQS8ufa/33AwG3ZoXip+V5g== dependencies: - "@smithy/config-resolver" "^1.0.2" - "@smithy/credential-provider-imds" "^1.0.2" - "@smithy/node-config-provider" "^1.0.2" - "@smithy/property-provider" "^1.0.2" - "@smithy/types" "^1.1.1" + "@smithy/config-resolver" "^1.0.1" + "@smithy/credential-provider-imds" "^1.0.1" + "@smithy/node-config-provider" "^1.0.1" + "@smithy/property-provider" "^1.0.1" + "@smithy/types" "^1.1.0" tslib "^2.5.0" "@smithy/util-hex-encoding@^1.0.1", "@smithy/util-hex-encoding@^1.0.2": @@ -2703,7 +2723,7 @@ dependencies: tslib "^2.5.0" -"@smithy/util-retry@^1.0.3", "@smithy/util-retry@^1.0.4": +"@smithy/util-retry@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-1.0.4.tgz#9d95df3884981414163d5f780d38e3529384d9ad" integrity sha512-RnZPVFvRoqdj2EbroDo3OsnnQU8eQ4AlnZTOGusbYKybH3269CFdrZfZJloe60AQjX7di3J6t/79PjwCLO5Khw== @@ -2711,6 +2731,28 @@ "@smithy/service-error-classification" "^1.0.3" tslib "^2.5.0" +"@smithy/util-stream-browser@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@smithy/util-stream-browser/-/util-stream-browser-1.0.1.tgz#95a1025b87837a6a0aea5adc9abeac47142c15d1" + integrity sha512-+VFPTeTB7ickwKFOO+FHd6iuMRlI3OGaL8q3K2+b2A/T+Hke61htS3SEHCD3KG7+vir96QFQJ8CZUprQgamvYQ== + dependencies: + "@smithy/fetch-http-handler" "^1.0.1" + "@smithy/types" "^1.1.0" + "@smithy/util-base64" "^1.0.1" + "@smithy/util-hex-encoding" "^1.0.1" + "@smithy/util-utf8" "^1.0.1" + tslib "^2.5.0" + +"@smithy/util-stream-node@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@smithy/util-stream-node/-/util-stream-node-1.0.2.tgz#4052da8abb6fc556f6d07cbe291297600957b7f7" + integrity sha512-HCawhaqLrqeFb/it0oZFWRT+yezgy0ZLyJ1nlZxCNbZdqiMsSIbkr5xyIYgEOHMcPh5/G+zUSpVXbOTSCX26TA== + dependencies: + "@smithy/node-http-handler" "^1.0.2" + "@smithy/types" "^1.1.0" + "@smithy/util-buffer-from" "^1.0.1" + tslib "^2.5.0" + "@smithy/util-stream@^1.0.1", "@smithy/util-stream@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-1.0.2.tgz#2d33aa5168e51d1dd7937c32a09c8334d2da44d9" @@ -2725,7 +2767,7 @@ "@smithy/util-utf8" "^1.0.2" tslib "^2.5.0" -"@smithy/util-uri-escape@^1.0.2": +"@smithy/util-uri-escape@^1.0.1", "@smithy/util-uri-escape@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-1.0.2.tgz#c69a5423c9baa7a045a79372320bd40a437ac756" integrity sha512-k8C0BFNS9HpBMHSgUDnWb1JlCQcFG+PPlVBq9keP4Nfwv6a9Q0yAfASWqUCtzjuMj1hXeLhn/5ADP6JxnID1Pg== @@ -2965,9 +3007,9 @@ integrity sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw== "@types/node@*", "@types/node@>=10.0.0": - version "20.4.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.1.tgz#a6033a8718653c50ac4962977e14d0f984d9527d" - integrity sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg== + version "20.3.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.3.tgz#329842940042d2b280897150e023e604d11657d6" + integrity sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw== "@types/node@^12.12.54": version "12.20.55" @@ -3880,12 +3922,12 @@ axios@^1.0.0: form-data "^4.0.0" proxy-from-env "^1.1.0" -babel-jest@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.1.tgz#a7141ad1ed5ec50238f3cd36127636823111233a" - integrity sha512-qu+3bdPEQC6KZSPz+4Fyjbga5OODNcp49j6GKzG1EKbkfyJBxEYGVUmVGpwCSeGouG52R4EgYMLb6p9YeEEQ4A== +babel-jest@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.0.tgz#f97962732a729ca5cb26f610250c0cb4577bf3f8" + integrity sha512-Jj8Bq2yKsk11XLk06Nm8SdvYkAcecH+GuhxB8DnK5SncjHnJ88TQjSnGgE7jpajpnSvz9DZ6X8hXrDkD/6/TPQ== dependencies: - "@jest/transform" "^29.6.1" + "@jest/transform" "^29.6.0" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" babel-preset-jest "^29.5.0" @@ -4362,9 +4404,9 @@ camelcase@^6.0.0, camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001503: - version "1.0.30001515" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz#418aefeed9d024cd3129bfae0ccc782d4cb8f12b" - integrity sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA== + version "1.0.30001512" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz#7450843fb581c39f290305a83523c7a9ef0d4cb4" + integrity sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw== capital-case@^1.0.4: version "1.0.4" @@ -5517,9 +5559,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.4.431: - version "1.4.456" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.456.tgz#aab5f3b421a07a73cf646d28e63c41238fc45849" - integrity sha512-d+eSL4mT9m72cnDT/kfQj6Pv6Cid4pUVlLOl8esm2SZuXBgtXtUyvCfc9F++GHLWLoY4gMNqg+0IVAoQ3sosKA== + version "1.4.450" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.450.tgz#df232c961ee9bf4e8980f86e96a6e9f291720138" + integrity sha512-BLG5HxSELlrMx7dJ2s+8SFlsCtJp37Zpk2VAxyC6CZtbc+9AJeZHfYHbrlSgdXp6saQ8StMqOTEDaBKgA7u1sw== elliptic@^6.5.3: version "6.5.4" @@ -5952,17 +5994,17 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0, expect@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.1.tgz#64dd1c8f75e2c0b209418f2b8d36a07921adfdf1" - integrity sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g== +expect@^29.0.0, expect@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.0.tgz#a0c114e91d8b6e9fcfb2d830411958699125bd23" + integrity sha512-AV+HaBtnDJ2YEUhPPo25HyUHBLaetM+y/Dq6pEC8VPQyt1dK+k8MfGkMy46djy2bddcqESc1kl4/K1uLWSfk9g== dependencies: - "@jest/expect-utils" "^29.6.1" + "@jest/expect-utils" "^29.6.0" "@types/node" "*" jest-get-type "^29.4.3" - jest-matcher-utils "^29.6.1" - jest-message-util "^29.6.1" - jest-util "^29.6.1" + jest-matcher-utils "^29.6.0" + jest-message-util "^29.6.0" + jest-util "^29.6.0" exponential-backoff@^3.1.1: version "3.1.1" @@ -7573,75 +7615,75 @@ jest-changed-files@^29.5.0: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.1.tgz#861dab37e71a89907d1c0fabc54a0019738ed824" - integrity sha512-tPbYLEiBU4MYAL2XoZme/bgfUeotpDBd81lgHLCbDZZFaGmECk0b+/xejPFtmiBP87GgP/y4jplcRpbH+fgCzQ== +jest-circus@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.0.tgz#aa6369bd10aecc8ec68298bd14cf43ac4370958a" + integrity sha512-LtG45qEKhse2Ws5zNR4DnZATReLGQXzBZGZnJ0DU37p6d4wDhu41vvczCQ3Ou+llR6CRYDBshsubV7H4jZvIkw== dependencies: - "@jest/environment" "^29.6.1" - "@jest/expect" "^29.6.1" - "@jest/test-result" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/environment" "^29.6.0" + "@jest/expect" "^29.6.0" + "@jest/test-result" "^29.6.0" + "@jest/types" "^29.6.0" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^29.6.1" - jest-matcher-utils "^29.6.1" - jest-message-util "^29.6.1" - jest-runtime "^29.6.1" - jest-snapshot "^29.6.1" - jest-util "^29.6.1" + jest-each "^29.6.0" + jest-matcher-utils "^29.6.0" + jest-message-util "^29.6.0" + jest-runtime "^29.6.0" + jest-snapshot "^29.6.0" + jest-util "^29.6.0" p-limit "^3.1.0" - pretty-format "^29.6.1" + pretty-format "^29.6.0" pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" jest-cli@^29.5.0: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.1.tgz#99d9afa7449538221c71f358f0fdd3e9c6e89f72" - integrity sha512-607dSgTA4ODIN6go9w6xY3EYkyPFGicx51a69H7yfvt7lN53xNswEVLovq+E77VsTRi5fWprLH0yl4DJgE8Ing== + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.0.tgz#a885e3d5a0b12ba520f61f8496bb0c9c2ff97896" + integrity sha512-WvZIaanK/abkw6s01924DQ2QLwM5Q4Y4iPbSDb9Zg6smyXGqqcPQ7ft9X8D7B0jICz312eSzM6UlQNxuZJBrMw== dependencies: - "@jest/core" "^29.6.1" - "@jest/test-result" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/core" "^29.6.0" + "@jest/test-result" "^29.6.0" + "@jest/types" "^29.6.0" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.6.1" - jest-util "^29.6.1" - jest-validate "^29.6.1" + jest-config "^29.6.0" + jest-util "^29.6.0" + jest-validate "^29.6.0" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.1.tgz#d785344509065d53a238224c6cdc0ed8e2f2f0dd" - integrity sha512-XdjYV2fy2xYixUiV2Wc54t3Z4oxYPAELUzWnV6+mcbq0rh742X2p52pii5A3oeRzYjLnQxCsZmp0qpI6klE2cQ== +jest-config@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.0.tgz#0bca14b634919519a298a56c0ed1d200b9f0fa31" + integrity sha512-fKA4jM91PDqWVkMpb1FVKxIuhg3hC6hgaen57cr1rRZkR96dCatvJZsk3ik7/GNu9ERj9wgAspOmyvkFoGsZhA== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.6.1" - "@jest/types" "^29.6.1" - babel-jest "^29.6.1" + "@jest/test-sequencer" "^29.6.0" + "@jest/types" "^29.6.0" + babel-jest "^29.6.0" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.6.1" - jest-environment-node "^29.6.1" + jest-circus "^29.6.0" + jest-environment-node "^29.6.0" jest-get-type "^29.4.3" jest-regex-util "^29.4.3" - jest-resolve "^29.6.1" - jest-runner "^29.6.1" - jest-util "^29.6.1" - jest-validate "^29.6.1" + jest-resolve "^29.6.0" + jest-runner "^29.6.0" + jest-util "^29.6.0" + jest-validate "^29.6.0" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.6.1" + pretty-format "^29.6.0" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -7655,15 +7697,15 @@ jest-diff@^28.0.2: jest-get-type "^28.0.2" pretty-format "^28.1.3" -jest-diff@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.1.tgz#13df6db0a89ee6ad93c747c75c85c70ba941e545" - integrity sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg== +jest-diff@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.0.tgz#9fe219a2f73a62ed6ac1c1a58e4965dc66836c4b" + integrity sha512-ZRm7cd2m9YyZ0N3iMyuo1iUiprxQ/MFpYWXzEEj7hjzL3WnDffKW8192XBDcrAI8j7hnrM1wed3bL/oEnYF/8w== dependencies: chalk "^4.0.0" diff-sequences "^29.4.3" jest-get-type "^29.4.3" - pretty-format "^29.6.1" + pretty-format "^29.6.0" jest-docblock@^29.4.3: version "29.4.3" @@ -7672,16 +7714,16 @@ jest-docblock@^29.4.3: dependencies: detect-newline "^3.0.0" -jest-each@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.1.tgz#975058e5b8f55c6780beab8b6ab214921815c89c" - integrity sha512-n5eoj5eiTHpKQCAVcNTT7DRqeUmJ01hsAL0Q1SMiBHcBcvTKDELixQOGMCpqhbIuTcfC4kMfSnpmDqRgRJcLNQ== +jest-each@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.0.tgz#320637063b518a51e42b38a0186255e6e5978fe7" + integrity sha512-d0Jem4RBAlFUyV6JSXPSHVUpNo5RleSj+iJEy1G3+ZCrzHDjWs/1jUfrbnJKHdJdAx5BCEce/Ju379WqHhQk4w== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.0" chalk "^4.0.0" jest-get-type "^29.4.3" - jest-util "^29.6.1" - pretty-format "^29.6.1" + jest-util "^29.6.0" + pretty-format "^29.6.0" jest-environment-jsdom@29.5.0: version "29.5.0" @@ -7697,17 +7739,17 @@ jest-environment-jsdom@29.5.0: jest-util "^29.5.0" jsdom "^20.0.0" -jest-environment-node@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.1.tgz#08a122dece39e58bc388da815a2166c58b4abec6" - integrity sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ== +jest-environment-node@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.0.tgz#634c3027afaa6f4211516348c59642d74b126a1b" + integrity sha512-BOf5Q2/nFCdBOnyBM5c5/6DbdQYgc+0gyUQ8l8qhUAB8O7pM+4QJXIXJsRZJaxd5SHV6y5VArTVhOfogoqcP8Q== dependencies: - "@jest/environment" "^29.6.1" - "@jest/fake-timers" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/environment" "^29.6.0" + "@jest/fake-timers" "^29.6.0" + "@jest/types" "^29.6.0" "@types/node" "*" - jest-mock "^29.6.1" - jest-util "^29.6.1" + jest-mock "^29.6.0" + jest-util "^29.6.0" jest-get-type@^28.0.2: version "28.0.2" @@ -7719,66 +7761,66 @@ jest-get-type@^29.4.3: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== -jest-haste-map@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.1.tgz#62655c7a1c1b349a3206441330fb2dbdb4b63803" - integrity sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig== +jest-haste-map@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.0.tgz#5f3e6292bc45f596de48835489ddac409748b15a" + integrity sha512-dY1DKufptj7hcJSuhpqlYPGcnN3XjlOy/g0jinpRTMsbb40ivZHiuIPzeminOZkrek8C+oDxC54ILGO3vMLojg== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.0" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" jest-regex-util "^29.4.3" - jest-util "^29.6.1" - jest-worker "^29.6.1" + jest-util "^29.6.0" + jest-worker "^29.6.0" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.1.tgz#66a902c81318e66e694df7d096a95466cb962f8e" - integrity sha512-OrxMNyZirpOEwkF3UHnIkAiZbtkBWiye+hhBweCHkVbCgyEy71Mwbb5zgeTNYWJBi1qgDVfPC1IwO9dVEeTLwQ== +jest-leak-detector@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.0.tgz#9b96d275622739b4436ee7e91b3f3d386471105c" + integrity sha512-JdV6EZOPxHR1gd6ccxjNowuROkT2jtGU5G/g58RcJX1xe5mrtLj0g6/ZkyMoXF4cs+tTkHMFX6pcIrB1QPQwCw== dependencies: jest-get-type "^29.4.3" - pretty-format "^29.6.1" + pretty-format "^29.6.0" -jest-matcher-utils@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz#6c60075d84655d6300c5d5128f46531848160b53" - integrity sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA== +jest-matcher-utils@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.0.tgz#4465344800591022a5239f529857c053da6a9d5c" + integrity sha512-oSlqfGN+sbkB2Q5um/zL7z80w84FEAcLKzXBZIPyRk2F2Srg1ubhrHVKW68JCvb2+xKzAeGw35b+6gciS24PHw== dependencies: chalk "^4.0.0" - jest-diff "^29.6.1" + jest-diff "^29.6.0" jest-get-type "^29.4.3" - pretty-format "^29.6.1" + pretty-format "^29.6.0" -jest-message-util@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.1.tgz#d0b21d87f117e1b9e165e24f245befd2ff34ff8d" - integrity sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ== +jest-message-util@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.0.tgz#b23c1f787fcc226c49489fd53018100c2f434fe6" + integrity sha512-mkCp56cETbpoNtsaeWVy6SKzk228mMi9FPHSObaRIhbR2Ujw9PqjW/yqVHD2tN1bHbC8ol6h3UEo7dOPmIYwIA== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.0" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^29.6.1" + pretty-format "^29.6.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.5.0, jest-mock@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.1.tgz#049ee26aea8cbf54c764af649070910607316517" - integrity sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw== +jest-mock@^29.5.0, jest-mock@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.0.tgz#4643fe35a3f20ef9a71f2a61f037a2ff05702d55" + integrity sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.0" "@types/node" "*" - jest-util "^29.6.1" + jest-util "^29.6.0" jest-pnp-resolver@^1.2.2: version "1.2.3" @@ -7790,147 +7832,147 @@ jest-regex-util@^29.4.3: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== -jest-resolve-dependencies@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.1.tgz#b85b06670f987a62515bbf625d54a499e3d708f5" - integrity sha512-BbFvxLXtcldaFOhNMXmHRWx1nXQO5LoXiKSGQcA1LxxirYceZT6ch8KTE1bK3X31TNG/JbkI7OkS/ABexVahiw== +jest-resolve-dependencies@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.0.tgz#772a315ebf2556c3c0ced98f268d2f931efab8a5" + integrity sha512-eOfPog9K3hJdJk/3i6O6bQhXS+3uXhMDkLJGX+xmMPp7T1d/zdcFofbDnHgNoEkhD/mSimC5IagLEP7lpLLu/A== dependencies: jest-regex-util "^29.4.3" - jest-snapshot "^29.6.1" + jest-snapshot "^29.6.0" -jest-resolve@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.1.tgz#4c3324b993a85e300add2f8609f51b80ddea39ee" - integrity sha512-AeRkyS8g37UyJiP9w3mmI/VXU/q8l/IH52vj/cDAyScDcemRbSBhfX/NMYIGilQgSVwsjxrCHf3XJu4f+lxCMg== +jest-resolve@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.0.tgz#e7ffd4ebfd03d0ef442eba00611b5a5ea18996b5" + integrity sha512-+hrpY4LzAONoZA/rvB6rnZLkOSA6UgJLpdCWrOZNSgGxWMumzRLu7dLUSCabAHzoHIDQ9qXfr3th1zYNJ0E8sQ== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.6.1" + jest-haste-map "^29.6.0" jest-pnp-resolver "^1.2.2" - jest-util "^29.6.1" - jest-validate "^29.6.1" + jest-util "^29.6.0" + jest-validate "^29.6.0" resolve "^1.20.0" resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.1.tgz#54557087e7972d345540d622ab5bfc3d8f34688c" - integrity sha512-tw0wb2Q9yhjAQ2w8rHRDxteryyIck7gIzQE4Reu3JuOBpGp96xWgF0nY8MDdejzrLCZKDcp8JlZrBN/EtkQvPQ== - dependencies: - "@jest/console" "^29.6.1" - "@jest/environment" "^29.6.1" - "@jest/test-result" "^29.6.1" - "@jest/transform" "^29.6.1" - "@jest/types" "^29.6.1" +jest-runner@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.0.tgz#7d8680b80c92c6fb94b9960714cd7004de7ef948" + integrity sha512-4fZuGV2lOxS2BiqEG9/AI8E6O+jo+QZjMVcgi1x5E6aDql0Gd/EFIbUQ0pSS09y8cya1vJB/qC2xsE468jqtSg== + dependencies: + "@jest/console" "^29.6.0" + "@jest/environment" "^29.6.0" + "@jest/test-result" "^29.6.0" + "@jest/transform" "^29.6.0" + "@jest/types" "^29.6.0" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" jest-docblock "^29.4.3" - jest-environment-node "^29.6.1" - jest-haste-map "^29.6.1" - jest-leak-detector "^29.6.1" - jest-message-util "^29.6.1" - jest-resolve "^29.6.1" - jest-runtime "^29.6.1" - jest-util "^29.6.1" - jest-watcher "^29.6.1" - jest-worker "^29.6.1" + jest-environment-node "^29.6.0" + jest-haste-map "^29.6.0" + jest-leak-detector "^29.6.0" + jest-message-util "^29.6.0" + jest-resolve "^29.6.0" + jest-runtime "^29.6.0" + jest-util "^29.6.0" + jest-watcher "^29.6.0" + jest-worker "^29.6.0" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.1.tgz#8a0fc9274ef277f3d70ba19d238e64334958a0dc" - integrity sha512-D6/AYOA+Lhs5e5il8+5pSLemjtJezUr+8zx+Sn8xlmOux3XOqx4d8l/2udBea8CRPqqrzhsKUsN/gBDE/IcaPQ== +jest-runtime@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.0.tgz#0f6d98b40625c620c6fa1f075c3b0ca95daa8f1c" + integrity sha512-5FavYo3EeXLHIvnJf+r7Cj0buePAbe4mzRB9oeVxDS0uVmouSBjWeGgyRjZkw7ArxOoZI8gO6f8SGMJ2HFlwwg== dependencies: - "@jest/environment" "^29.6.1" - "@jest/fake-timers" "^29.6.1" - "@jest/globals" "^29.6.1" + "@jest/environment" "^29.6.0" + "@jest/fake-timers" "^29.6.0" + "@jest/globals" "^29.6.0" "@jest/source-map" "^29.6.0" - "@jest/test-result" "^29.6.1" - "@jest/transform" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/test-result" "^29.6.0" + "@jest/transform" "^29.6.0" + "@jest/types" "^29.6.0" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.6.1" - jest-message-util "^29.6.1" - jest-mock "^29.6.1" + jest-haste-map "^29.6.0" + jest-message-util "^29.6.0" + jest-mock "^29.6.0" jest-regex-util "^29.4.3" - jest-resolve "^29.6.1" - jest-snapshot "^29.6.1" - jest-util "^29.6.1" + jest-resolve "^29.6.0" + jest-snapshot "^29.6.0" + jest-util "^29.6.0" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.1.tgz#0d083cb7de716d5d5cdbe80d598ed2fbafac0239" - integrity sha512-G4UQE1QQ6OaCgfY+A0uR1W2AY0tGXUPQpoUClhWHq1Xdnx1H6JOrC2nH5lqnOEqaDgbHFgIwZ7bNq24HpB180A== +jest-snapshot@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.0.tgz#a8653fe098f1c39ab37c94f8b1370f606b5618a9" + integrity sha512-H3kUE9NwWDEDoutcOSS921IqdlkdjgnMdj1oMyxAHNflscdLc9dB8OudZHV6kj4OHJxbMxL8CdI5DlwYrs4wQg== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.6.1" - "@jest/transform" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/expect-utils" "^29.6.0" + "@jest/transform" "^29.6.0" + "@jest/types" "^29.6.0" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.6.1" + expect "^29.6.0" graceful-fs "^4.2.9" - jest-diff "^29.6.1" + jest-diff "^29.6.0" jest-get-type "^29.4.3" - jest-matcher-utils "^29.6.1" - jest-message-util "^29.6.1" - jest-util "^29.6.1" + jest-matcher-utils "^29.6.0" + jest-message-util "^29.6.0" + jest-util "^29.6.0" natural-compare "^1.4.0" - pretty-format "^29.6.1" + pretty-format "^29.6.0" semver "^7.5.3" -jest-util@^29.0.0, jest-util@^29.5.0, jest-util@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.1.tgz#c9e29a87a6edbf1e39e6dee2b4689b8a146679cb" - integrity sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg== +jest-util@^29.0.0, jest-util@^29.5.0, jest-util@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.0.tgz#4071050c5d70f5d4d48105e8883773f3a6b94f8d" + integrity sha512-S0USx9YwcvEm4pQ5suisVm/RVxBmi0GFR7ocJhIeaCuW5AXnAnffXbaVKvIFodyZNOc9ygzVtTxmBf40HsHXaA== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.0" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.1.tgz#765e684af6e2c86dce950aebefbbcd4546d69f7b" - integrity sha512-r3Ds69/0KCN4vx4sYAbGL1EVpZ7MSS0vLmd3gV78O+NAx3PDQQukRU5hNHPXlyqCgFY8XUk7EuTMLugh0KzahA== +jest-validate@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.0.tgz#6a7416a1df4fe90896db566b83d6b4c9485c402c" + integrity sha512-MLTrAJsb1+W7svbeZ+A7pAnyXMaQrjvPDKCy7OlfsfB6TMVc69v7WjUWfiR6r3snULFWZASiKgvNVDuATta1dg== dependencies: - "@jest/types" "^29.6.1" + "@jest/types" "^29.6.0" camelcase "^6.2.0" chalk "^4.0.0" jest-get-type "^29.4.3" leven "^3.1.0" - pretty-format "^29.6.1" + pretty-format "^29.6.0" -jest-watcher@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.1.tgz#7c0c43ddd52418af134c551c92c9ea31e5ec942e" - integrity sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA== +jest-watcher@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.0.tgz#77df9ffcdfc70406fdd577020c1e4d62de5a0299" + integrity sha512-LdsQqFNX60mRdRRe+zsELnYRH1yX6KL+ukbh+u6WSQeTheZZe1TlLJNKRQiZ7e0VbvMkywmMWL/KV35noOJCcw== dependencies: - "@jest/test-result" "^29.6.1" - "@jest/types" "^29.6.1" + "@jest/test-result" "^29.6.0" + "@jest/types" "^29.6.0" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.6.1" + jest-util "^29.6.0" string-length "^4.0.1" jest-websocket-mock@^2.0.2: @@ -7950,13 +7992,13 @@ jest-worker@^27.4.5, jest-worker@^27.4.6: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.1.tgz#64b015f0e985ef3a8ad049b61fe92b3db74a5319" - integrity sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA== +jest-worker@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.0.tgz#e0c40226d073fdb8f0dfe87d7f90f8fd987d8ba3" + integrity sha512-oiQHH1SnKmZIwwPnpOrXTq4kHBk3lKGY/07DpnH0sAu+x7J8rXlbLDROZsU6vy9GwB0hPiZeZpu6YlJ48QoKcA== dependencies: "@types/node" "*" - jest-util "^29.6.1" + jest-util "^29.6.0" merge-stream "^2.0.0" supports-color "^8.0.0" @@ -9509,9 +9551,9 @@ number-is-nan@^1.0.0: integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== nwsapi@^2.2.2: - version "2.2.7" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" - integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== + version "2.2.6" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.6.tgz#f876bd7ae9509cac72c640826355abf63d3c326a" + integrity sha512-vSZ4miHQ4FojLjmz2+ux4B0/XA16jfwt/LBzIUftDpRd8tujHFkXjMyLwjS08fIZCzesj2z7gJukOKJwqebJAQ== nx@15.9.4, "nx@>=14.6.1 < 16": version "15.9.4" @@ -10152,10 +10194,10 @@ pretty-format@^28.1.3: ansi-styles "^5.0.0" react-is "^18.0.0" -pretty-format@^29.0.0, pretty-format@^29.6.1: - version "29.6.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.1.tgz#ec838c288850b7c4f9090b867c2d4f4edbfb0f3e" - integrity sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog== +pretty-format@^29.0.0, pretty-format@^29.6.0: + version "29.6.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.0.tgz#c90c8f145187fe73240662527a513599c16f3b97" + integrity sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ== dependencies: "@jest/schemas" "^29.6.0" ansi-styles "^5.0.0" @@ -10939,9 +10981,9 @@ semver@7.5.2: lru-cache "^6.0.0" semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.0, semver@^7.5.3: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + version "7.5.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" + integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== dependencies: lru-cache "^6.0.0" @@ -12069,9 +12111,9 @@ typedoc@0.23.23: integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== typescript@next: - version "5.2.0-dev.20230711" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.0-dev.20230711.tgz#6fbedbdd04ffb4eb6e1fe7ac70485a525d5413c7" - integrity sha512-Pu1roPsNZOmZvIeoPHiwFqO31X+LQYLXjQgADk/wXu4qZ0/ovCuMVA3+3CYnrvsBecjLU9UN0pC4JFUyvfN6lA== + version "5.2.0-dev.20230705" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.0-dev.20230705.tgz#a793c11eef3a867de2c31484b4f89c8aaabdd31f" + integrity sha512-eMgfQ/e5CgYYSaduGhDc2UiY2EzzgrBMfHyagjUP70jj4VDCmLlIp1EftT5AMiLvNc7PYNCeziRFY5fBZhD9uA== typescript@~4.8.4: version "4.8.4"