diff --git a/.changeset/funny-cars-dance.md b/.changeset/funny-cars-dance.md new file mode 100644 index 00000000000..ae1cad3c6e9 --- /dev/null +++ b/.changeset/funny-cars-dance.md @@ -0,0 +1,6 @@ +--- +"@smithy/smithy-client": minor +"@smithy/types": minor +--- + +allow adding new checksum algorithms via extension diff --git a/packages/smithy-client/package.json b/packages/smithy-client/package.json index 9438886181b..34bdc4561a2 100644 --- a/packages/smithy-client/package.json +++ b/packages/smithy-client/package.json @@ -13,7 +13,9 @@ "format": "prettier --config ../../prettier.config.js --ignore-path ../../.prettierignore --write \"**/*.{ts,md,json}\"", "extract:docs": "node ./scripts/fix-api-extractor && api-extractor run --local && node ./scripts/fix-api-extractor --unset", "test": "yarn g:vitest run", - "test:watch": "yarn g:vitest watch" + "test:watch": "yarn g:vitest watch", + "test:integration": "yarn g:vitest run -c vitest.config.integ.mts", + "test:integration:watch": "yarn g:vitest watch -c vitest.config.integ.mts" }, "main": "./dist-cjs/index.js", "module": "./dist-es/index.js", diff --git a/packages/smithy-client/src/extensions/checksum.integ.spec.ts b/packages/smithy-client/src/extensions/checksum.integ.spec.ts new file mode 100644 index 00000000000..3cd4cef4bec --- /dev/null +++ b/packages/smithy-client/src/extensions/checksum.integ.spec.ts @@ -0,0 +1,70 @@ +import { RpcV2Protocol } from "@smithy/smithy-rpcv2-cbor-schema"; +import type { Checksum } from "@smithy/types"; +import { describe, expect, test as it } from "vitest"; + +import type { PartialChecksumRuntimeConfigType } from "./checksum"; + +describe("checksum extension", () => { + it("should allow definition of new checksum algorithms via runtime extension", async () => { + class Sha256Custom implements Checksum { + update() {} + async digest() { + return new Uint8Array(4); + } + reset() {} + } + + class R1 { + update() {} + async digest() { + return new Uint8Array(4); + } + reset() {} + } + + const client = new RpcV2Protocol({ + endpoint: "https://localhost", + extensions: [ + { + configure(ext) { + ext.addChecksumAlgorithm({ + algorithmId() { + return "r1"; + }, + checksumConstructor() { + return R1; + }, + }); + ext.addChecksumAlgorithm({ + algorithmId() { + return "sha256"; + }, + checksumConstructor() { + return Sha256Custom; + }, + }); + }, + }, + ], + }); + + const config = client.config as typeof client.config & PartialChecksumRuntimeConfigType; + + expect(config.checksumAlgorithms).toEqual({ + // the algo id is used as the key if it is not recognized. + r1: R1, + + // Rhe uppercase form is used if it is recognized. + // This matches the key in the algorithm selector function. + SHA256: Sha256Custom, + }); + + // for known algorithms that exist on the config, they are also set by the extension. + expect(config.sha256).toEqual(Sha256Custom); + expect(config.md5).toEqual(undefined); + expect(config.sha1).toEqual(undefined); + + // for novel algorithms, they are not set to new fields on the config. + expect((config as any).r1).toEqual(undefined); + }); +}); diff --git a/packages/smithy-client/src/extensions/checksum.ts b/packages/smithy-client/src/extensions/checksum.ts index 3c205c32d96..133aff40d0a 100644 --- a/packages/smithy-client/src/extensions/checksum.ts +++ b/packages/smithy-client/src/extensions/checksum.ts @@ -6,17 +6,24 @@ export { AlgorithmId, ChecksumAlgorithm, ChecksumConfiguration }; /** * @internal */ -export type PartialChecksumRuntimeConfigType = Partial<{ - sha256: ChecksumConstructor | HashConstructor; - md5: ChecksumConstructor | HashConstructor; - crc32: ChecksumConstructor | HashConstructor; - crc32c: ChecksumConstructor | HashConstructor; - sha1: ChecksumConstructor | HashConstructor; -}>; +const knownAlgorithms: string[] = Object.values(AlgorithmId); /** * @internal */ +export type PartialChecksumRuntimeConfigType = { + checksumAlgorithms?: Record; + sha256?: ChecksumConstructor | HashConstructor; + md5?: ChecksumConstructor | HashConstructor; + crc32?: ChecksumConstructor | HashConstructor; + crc32c?: ChecksumConstructor | HashConstructor; + sha1?: ChecksumConstructor | HashConstructor; +}; + +/** + * @param runtimeConfig - config object of the client instance. + * @internal + */ export const getChecksumConfiguration = (runtimeConfig: PartialChecksumRuntimeConfigType) => { const checksumAlgorithms: ChecksumAlgorithm[] = []; @@ -30,9 +37,22 @@ export const getChecksumConfiguration = (runtimeConfig: PartialChecksumRuntimeCo checksumConstructor: () => runtimeConfig[algorithmId]!, }); } - + for (const [id, ChecksumCtor] of Object.entries(runtimeConfig.checksumAlgorithms ?? {})) { + checksumAlgorithms.push({ + algorithmId: () => id, + checksumConstructor: () => ChecksumCtor, + }); + } return { addChecksumAlgorithm(algo: ChecksumAlgorithm): void { + runtimeConfig.checksumAlgorithms = runtimeConfig.checksumAlgorithms ?? {}; + const id = algo.algorithmId(); + const ctor = algo.checksumConstructor(); + if (knownAlgorithms.includes(id)) { + runtimeConfig.checksumAlgorithms[id.toUpperCase()] = ctor; + } else { + runtimeConfig.checksumAlgorithms[id] = ctor; + } checksumAlgorithms.push(algo); }, checksumAlgorithms(): ChecksumAlgorithm[] { @@ -47,7 +67,11 @@ export const getChecksumConfiguration = (runtimeConfig: PartialChecksumRuntimeCo export const resolveChecksumRuntimeConfig = (clientConfig: ChecksumConfiguration): PartialChecksumRuntimeConfigType => { const runtimeConfig: PartialChecksumRuntimeConfigType = {}; clientConfig.checksumAlgorithms().forEach((checksumAlgorithm) => { - runtimeConfig[checksumAlgorithm.algorithmId()] = checksumAlgorithm.checksumConstructor(); + const id = checksumAlgorithm.algorithmId(); + if (knownAlgorithms.includes(id)) { + runtimeConfig[id as AlgorithmId] = checksumAlgorithm.checksumConstructor(); + } + // else the algorithm was attached to the checksumAlgorithms object on the client config already. }); return runtimeConfig; diff --git a/packages/smithy-client/vitest.config.integ.mts b/packages/smithy-client/vitest.config.integ.mts new file mode 100644 index 00000000000..5802db1ac64 --- /dev/null +++ b/packages/smithy-client/vitest.config.integ.mts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["**/*.integ.spec.ts"], + environment: "node", + }, +}); diff --git a/packages/types/src/extensions/checksum.ts b/packages/types/src/extensions/checksum.ts index 39c4fb20623..c8ae3c8b491 100644 --- a/packages/types/src/extensions/checksum.ts +++ b/packages/types/src/extensions/checksum.ts @@ -16,7 +16,7 @@ export enum AlgorithmId { * @internal */ export interface ChecksumAlgorithm { - algorithmId(): AlgorithmId; + algorithmId(): AlgorithmId | string; checksumConstructor(): ChecksumConstructor | HashConstructor; }