From 7bb1e82bab2f7e217db09a9241a72160398cb9e6 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Fri, 14 Apr 2023 15:18:02 +0000 Subject: [PATCH 1/2] feat:added adaptive types to SecretsProvider --- .../parameters/src/secrets/SecretsProvider.ts | 28 ++++++----- packages/parameters/src/secrets/getSecret.ts | 21 ++++++-- .../parameters/src/types/SecretsProvider.ts | 43 +++++++++++++++-- .../parameters/tests/unit/getSecret.test.ts | 48 ++++++++++++++++++- 4 files changed, 118 insertions(+), 22 deletions(-) diff --git a/packages/parameters/src/secrets/SecretsProvider.ts b/packages/parameters/src/secrets/SecretsProvider.ts index c8d1e56b77..7fb725a7b1 100644 --- a/packages/parameters/src/secrets/SecretsProvider.ts +++ b/packages/parameters/src/secrets/SecretsProvider.ts @@ -6,7 +6,8 @@ import { import type { GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager'; import type { SecretsProviderOptions, - SecretsGetOptionsInterface + SecretsGetOptions, + SecretsGetOutput, } from '../types/SecretsProvider'; /** @@ -157,7 +158,7 @@ class SecretsProvider extends BaseProvider { * * @param {SecretsProviderOptions} config - The configuration object. */ - public constructor (config?: SecretsProviderOptions) { + public constructor(config?: SecretsProviderOptions) { super(); if (config?.awsSdkV3Client) { @@ -170,7 +171,6 @@ class SecretsProvider extends BaseProvider { const clientConfig = config?.clientConfig || {}; this.client = new SecretsManagerClient(clientConfig); } - } /** @@ -197,14 +197,20 @@ class SecretsProvider extends BaseProvider { * For usage examples check {@link SecretsProvider}. * * @param {string} name - The name of the secret - * @param {SecretsGetOptionsInterface} options - Options to customize the retrieval of the secret + * @param {SecretsGetOptions} options - Options to customize the retrieval of the secret * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ - public async get( + public async get< + ExplicitUserProvidedType = undefined, + InferredFromOptionsType extends SecretsGetOptions | undefined = SecretsGetOptions + >( name: string, - options?: SecretsGetOptionsInterface - ): Promise> { - return super.get(name, options); + options?: InferredFromOptionsType & SecretsGetOptions + ): Promise | undefined> { + return super.get( + name, + options + ) as Promise | undefined>; } /** @@ -221,11 +227,11 @@ class SecretsProvider extends BaseProvider { * Retrieve a configuration from AWS AppConfig. * * @param {string} name - Name of the configuration or its ID - * @param {SecretsGetOptionsInterface} options - SDK options to propagate to the AWS SDK v3 for JavaScript client + * @param {SecretsGetOptions} options - SDK options to propagate to the AWS SDK v3 for JavaScript client */ protected async _get( name: string, - options?: SecretsGetOptionsInterface + options?: SecretsGetOptions ): Promise { const sdkOptions: GetSecretValueCommandInput = { ...(options?.sdkOptions || {}), @@ -249,7 +255,7 @@ class SecretsProvider extends BaseProvider { _options?: unknown ): Promise> { throw new Error('Method not implemented.'); - } + } } export { diff --git a/packages/parameters/src/secrets/getSecret.ts b/packages/parameters/src/secrets/getSecret.ts index a6b39b3229..16586d0f49 100644 --- a/packages/parameters/src/secrets/getSecret.ts +++ b/packages/parameters/src/secrets/getSecret.ts @@ -1,6 +1,9 @@ import { DEFAULT_PROVIDERS } from '../BaseProvider'; import { SecretsProvider } from './SecretsProvider'; -import type { SecretsGetOptionsInterface } from '../types/SecretsProvider'; +import type { + SecretsGetOptions, + SecretsGetOutput, +} from '../types/SecretsProvider'; /** * ## Intro @@ -100,15 +103,23 @@ import type { SecretsGetOptionsInterface } from '../types/SecretsProvider'; * * * @param {string} name - The name of the secret to retrieve - * @param {SecretsGetOptionsInterface} options - Options to configure the provider + * @param {SecretsGetOptions} options - Options to configure the provider * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ -const getSecret = async (name: string, options?: SecretsGetOptionsInterface): Promise> => { +const getSecret = async < + ExplicitUserProvidedType = undefined, + InferredFromOptionsType extends SecretsGetOptions | undefined = SecretsGetOptions +>( + name: string, + options?: InferredFromOptionsType & SecretsGetOptions +): Promise | undefined> => { if (!DEFAULT_PROVIDERS.hasOwnProperty('secrets')) { DEFAULT_PROVIDERS.secrets = new SecretsProvider(); } - - return DEFAULT_PROVIDERS.secrets.get(name, options); + + return ( + DEFAULT_PROVIDERS.secrets as SecretsProvider + ).get(name, options) as Promise | undefined>; }; export { diff --git a/packages/parameters/src/types/SecretsProvider.ts b/packages/parameters/src/types/SecretsProvider.ts index 152dff709b..b0d54bcb21 100644 --- a/packages/parameters/src/types/SecretsProvider.ts +++ b/packages/parameters/src/types/SecretsProvider.ts @@ -1,5 +1,12 @@ -import type { GetOptionsInterface } from './BaseProvider'; -import type { SecretsManagerClient, SecretsManagerClientConfig, GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager'; +import type { + GetOptionsInterface, + TransformOptions +} from './BaseProvider'; +import type { + SecretsManagerClient, + SecretsManagerClientConfig, + GetSecretValueCommandInput +} from '@aws-sdk/client-secrets-manager'; /** * Base interface for SecretsProviderOptions. @@ -45,11 +52,39 @@ type SecretsProviderOptions = SecretsProviderOptionsWithClientConfig | SecretsPr * @property {GetSecretValueCommandInput} sdkOptions - Options to pass to the underlying SDK. * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. */ -interface SecretsGetOptionsInterface extends GetOptionsInterface { +interface SecretsGetOptions extends GetOptionsInterface { + /** + * Additional options to pass to the AWS SDK v3 client. Supports all options from `GetSecretValueCommandInput`. + */ sdkOptions?: Omit, 'SecretId'> + transform?: Exclude } +interface SecretsGetOptionsTransformJson extends SecretsGetOptions { + transform: 'json' +} + +interface SecretsGetOptionsTransformBinary extends SecretsGetOptions { + transform: 'binary' +} + +interface SecretsGetOptionsTransformNone extends SecretsGetOptions { + transform?: never +} + +/** + * Generic output type for the SecretsProvider get method. + */ +type SecretsGetOutput = + undefined extends ExplicitUserProvidedType ? + undefined extends InferredFromOptionsType ? string : + InferredFromOptionsType extends SecretsGetOptionsTransformNone | SecretsGetOptionsTransformBinary ? string : + InferredFromOptionsType extends SecretsGetOptionsTransformJson ? Record : + never + : ExplicitUserProvidedType; + export type { SecretsProviderOptions, - SecretsGetOptionsInterface, + SecretsGetOptions, + SecretsGetOutput, }; \ No newline at end of file diff --git a/packages/parameters/tests/unit/getSecret.test.ts b/packages/parameters/tests/unit/getSecret.test.ts index 8fcbd634ad..5a3cdb1c71 100644 --- a/packages/parameters/tests/unit/getSecret.test.ts +++ b/packages/parameters/tests/unit/getSecret.test.ts @@ -29,7 +29,7 @@ describe('Function: getSecret', () => { }); // Act - const result = await getSecret(secretName); + const result: string | undefined = await getSecret(secretName); // Assess expect(client).toReceiveCommandWith(GetSecretValueCommand, { SecretId: secretName }); @@ -50,7 +50,7 @@ describe('Function: getSecret', () => { }); // Act - const result = await getSecret(secretName); + const result: string | undefined = await getSecret(secretName); // Assess expect(client).toReceiveCommandWith(GetSecretValueCommand, { SecretId: secretName }); @@ -59,4 +59,48 @@ describe('Function: getSecret', () => { }); + test('when called and transform `JSON` is specified, it returns an object with correct type', async () => { + + // Prepare + const provider = new SecretsProvider(); + DEFAULT_PROVIDERS.secrets = provider; + const secretName = 'foo'; + const secretValue = JSON.stringify({ hello: 'world' }); + const client = mockClient(SecretsManagerClient).on(GetSecretValueCommand).resolves({ + SecretString: secretValue, + }); + + // Act + const value: Record | undefined = await getSecret(secretName, { transform: 'json' }); + + // Assess + expect(client).toReceiveCommandWith(GetSecretValueCommand, { + SecretId: secretName, + }); + expect(value).toStrictEqual(JSON.parse(secretValue)); + + }); + + test('when called and transform `JSON` is specified as well as an explicit `K` type, it returns a result with correct type', async () => { + + // Prepare + const provider = new SecretsProvider(); + DEFAULT_PROVIDERS.secrets = provider; + const secretName = 'foo'; + const secretValue = JSON.stringify(5); + const client = mockClient(SecretsManagerClient).on(GetSecretValueCommand).resolves({ + SecretString: secretValue, + }); + + // Act + const value: number | undefined = await getSecret(secretName, { transform: 'json' }); + + // Assess + expect(client).toReceiveCommandWith(GetSecretValueCommand, { + SecretId: secretName, + }); + expect(value).toBe(JSON.parse(secretValue)); + + }); + }); \ No newline at end of file From b59f9f8116e3b7612ec8293a72cc4d5c30f8ef36 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Fri, 14 Apr 2023 15:47:53 +0000 Subject: [PATCH 2/2] chore: added Uint8Array type to return --- .../parameters/src/secrets/SecretsProvider.ts | 3 ++- packages/parameters/src/secrets/getSecret.ts | 3 ++- packages/parameters/src/types/SecretsProvider.ts | 16 ++++++++++++---- packages/parameters/tests/unit/getSecret.test.ts | 4 ++-- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/parameters/src/secrets/SecretsProvider.ts b/packages/parameters/src/secrets/SecretsProvider.ts index 7fb725a7b1..2144fe1268 100644 --- a/packages/parameters/src/secrets/SecretsProvider.ts +++ b/packages/parameters/src/secrets/SecretsProvider.ts @@ -8,6 +8,7 @@ import type { SecretsProviderOptions, SecretsGetOptions, SecretsGetOutput, + SecretsGetOptionsUnion, } from '../types/SecretsProvider'; /** @@ -202,7 +203,7 @@ class SecretsProvider extends BaseProvider { */ public async get< ExplicitUserProvidedType = undefined, - InferredFromOptionsType extends SecretsGetOptions | undefined = SecretsGetOptions + InferredFromOptionsType extends SecretsGetOptionsUnion | undefined = SecretsGetOptionsUnion >( name: string, options?: InferredFromOptionsType & SecretsGetOptions diff --git a/packages/parameters/src/secrets/getSecret.ts b/packages/parameters/src/secrets/getSecret.ts index 16586d0f49..812e165d5b 100644 --- a/packages/parameters/src/secrets/getSecret.ts +++ b/packages/parameters/src/secrets/getSecret.ts @@ -3,6 +3,7 @@ import { SecretsProvider } from './SecretsProvider'; import type { SecretsGetOptions, SecretsGetOutput, + SecretsGetOptionsUnion, } from '../types/SecretsProvider'; /** @@ -108,7 +109,7 @@ import type { */ const getSecret = async < ExplicitUserProvidedType = undefined, - InferredFromOptionsType extends SecretsGetOptions | undefined = SecretsGetOptions + InferredFromOptionsType extends SecretsGetOptionsUnion | undefined = SecretsGetOptionsUnion >( name: string, options?: InferredFromOptionsType & SecretsGetOptions diff --git a/packages/parameters/src/types/SecretsProvider.ts b/packages/parameters/src/types/SecretsProvider.ts index b0d54bcb21..07c23c82bc 100644 --- a/packages/parameters/src/types/SecretsProvider.ts +++ b/packages/parameters/src/types/SecretsProvider.ts @@ -72,19 +72,27 @@ interface SecretsGetOptionsTransformNone extends SecretsGetOptions { transform?: never } +type SecretsGetOptionsUnion = + SecretsGetOptionsTransformNone | + SecretsGetOptionsTransformJson | + SecretsGetOptionsTransformBinary | + undefined; + /** * Generic output type for the SecretsProvider get method. */ type SecretsGetOutput = undefined extends ExplicitUserProvidedType ? - undefined extends InferredFromOptionsType ? string : - InferredFromOptionsType extends SecretsGetOptionsTransformNone | SecretsGetOptionsTransformBinary ? string : - InferredFromOptionsType extends SecretsGetOptionsTransformJson ? Record : - never + undefined extends InferredFromOptionsType ? string | Uint8Array : + InferredFromOptionsType extends SecretsGetOptionsTransformNone ? string | Uint8Array : + InferredFromOptionsType extends SecretsGetOptionsTransformBinary ? string : + InferredFromOptionsType extends SecretsGetOptionsTransformJson ? Record : + never : ExplicitUserProvidedType; export type { SecretsProviderOptions, SecretsGetOptions, SecretsGetOutput, + SecretsGetOptionsUnion, }; \ No newline at end of file diff --git a/packages/parameters/tests/unit/getSecret.test.ts b/packages/parameters/tests/unit/getSecret.test.ts index 5a3cdb1c71..ad9360daae 100644 --- a/packages/parameters/tests/unit/getSecret.test.ts +++ b/packages/parameters/tests/unit/getSecret.test.ts @@ -29,7 +29,7 @@ describe('Function: getSecret', () => { }); // Act - const result: string | undefined = await getSecret(secretName); + const result: string | Uint8Array | undefined = await getSecret(secretName); // Assess expect(client).toReceiveCommandWith(GetSecretValueCommand, { SecretId: secretName }); @@ -50,7 +50,7 @@ describe('Function: getSecret', () => { }); // Act - const result: string | undefined = await getSecret(secretName); + const result: string | Uint8Array | undefined = await getSecret(secretName); // Assess expect(client).toReceiveCommandWith(GetSecretValueCommand, { SecretId: secretName });