diff --git a/packages/parameters/src/ssm/SSMProvider.ts b/packages/parameters/src/ssm/SSMProvider.ts index 14fd6793c2..ba1794e98f 100644 --- a/packages/parameters/src/ssm/SSMProvider.ts +++ b/packages/parameters/src/ssm/SSMProvider.ts @@ -15,8 +15,12 @@ import type { } from '@aws-sdk/client-ssm'; import type { SSMProviderOptions, - SSMGetMultipleOptionsInterface, - SSMGetOptionsInterface, + SSMGetOptions, + SSMGetOutput, + SSMGetMultipleOptions, + SSMGetMultipleOptionsUnion, + SSMGetMultipleOutput, + SSMGetParametersByNameOutput, SSMGetParametersByNameOutputInterface, SSMGetParametersByNameOptionsInterface, SSMSplitBatchAndDecryptParametersOutputType, @@ -312,14 +316,14 @@ class SSMProvider extends BaseProvider { * For usage examples check {@link SSMProvider}. * * @param {string} name - The name of the value to retrieve (i.e. the partition key) - * @param {SSMGetOptionsInterface} options - Options to configure the provider + * @param {SSMGetOptions} options - Options to configure the provider * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ - public async get( + public async get( name: string, - options?: SSMGetOptionsInterface | undefined - ): Promise | undefined> { - return super.get(name, options) as Promise | undefined>; + options?: O & SSMGetOptions + ): Promise | undefined> { + return super.get(name, options) as Promise | undefined>; } /** @@ -349,14 +353,14 @@ class SSMProvider extends BaseProvider { * For usage examples check {@link SSMProvider}. * * @param {string} path - The path of the parameters to retrieve - * @param {SSMGetMultipleOptionsInterface} options - Options to configure the retrieval + * @param {SSMGetMultipleOptions} options - Options to configure the retrieval * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ - public async getMultiple( + public async getMultiple( path: string, - options?: SSMGetMultipleOptionsInterface | undefined - ): Promise> { - return super.getMultiple(path, options); + options?: O & SSMGetMultipleOptions + ): Promise | undefined> { + return super.getMultiple(path, options) as Promise | undefined>; } /** @@ -409,10 +413,10 @@ class SSMProvider extends BaseProvider { * @param {SSMGetParametersByNameOptionsInterface} options - Options to configure the retrieval * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ - public async getParametersByName( + public async getParametersByName( parameters: Record, options?: SSMGetParametersByNameOptionsInterface - ): Promise> { + ): Promise> { const configs = { ...{ decrypt: this.resolveDecryptionConfigValue({}) || false, maxAge: DEFAULT_MAX_AGE_SECS, @@ -460,18 +464,18 @@ class SSMProvider extends BaseProvider { } } - return response; + return response as unknown as Promise>; } /** * Retrieve a parameter from AWS Systems Manager. * * @param {string} name - Name of the parameter to retrieve - * @param {SSMGetOptionsInterface} options - Options to customize the retrieval + * @param {SSMGetOptions} options - Options to customize the retrieval */ protected async _get( name: string, - options?: SSMGetOptionsInterface + options?: SSMGetOptions ): Promise { const sdkOptions: GetParameterCommandInput = { ...(options?.sdkOptions || {}), @@ -487,11 +491,11 @@ class SSMProvider extends BaseProvider { * Retrieve multiple items from AWS Systems Manager. * * @param {string} path - The path of the parameters to retrieve - * @param {SSMGetMultipleOptionsInterface} options - Options to configure the provider + * @param {SSMGetMultipleOptions} options - Options to configure the provider */ protected async _getMultiple( path: string, - options?: SSMGetMultipleOptionsInterface + options?: SSMGetMultipleOptions ): Promise> { const sdkOptions: GetParametersByPathCommandInput = { ...(options?.sdkOptions || {}), @@ -733,7 +737,7 @@ class SSMProvider extends BaseProvider { } protected resolveDecryptionConfigValue( - options: SSMGetOptionsInterface | SSMGetMultipleOptionsInterface = {}, + options: SSMGetOptions | SSMGetMultipleOptions = {}, sdkOptions?: GetParameterCommandInput | GetParametersByPathCommandInput ): boolean | undefined { if (options?.decrypt !== undefined) return options.decrypt; diff --git a/packages/parameters/src/ssm/getParameter.ts b/packages/parameters/src/ssm/getParameter.ts index 12b09e0c08..9e61bc0c6a 100644 --- a/packages/parameters/src/ssm/getParameter.ts +++ b/packages/parameters/src/ssm/getParameter.ts @@ -1,5 +1,8 @@ import { SSMProvider, DEFAULT_PROVIDERS } from './SSMProvider'; -import type { SSMGetOptionsInterface } from '../types/SSMProvider'; +import type { + SSMGetOptions, + SSMGetOutput, +} from '../types/SSMProvider'; /** * ## Intro @@ -133,18 +136,20 @@ import type { SSMGetOptionsInterface } from '../types/SSMProvider'; * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). * * @param {string} name - The name of the parameter to retrieve - * @param {SSMGetOptionsInterface} options - Options to configure the provider + * @param {SSMGetOptions} options - Options to configure the provider * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ -const getParameter = ( +const getParameter = ( name: string, - options?: SSMGetOptionsInterface -): Promise> => { + options?: O & SSMGetOptions +): Promise | undefined> => { if (!DEFAULT_PROVIDERS.hasOwnProperty('ssm')) { DEFAULT_PROVIDERS.ssm = new SSMProvider(); } - return (DEFAULT_PROVIDERS.ssm as SSMProvider).get(name, options); + return ( + DEFAULT_PROVIDERS.ssm as SSMProvider + ).get(name, options) as Promise | undefined>; }; export { diff --git a/packages/parameters/src/ssm/getParameters.ts b/packages/parameters/src/ssm/getParameters.ts index e93daf1ea8..1eb123dbbc 100644 --- a/packages/parameters/src/ssm/getParameters.ts +++ b/packages/parameters/src/ssm/getParameters.ts @@ -1,5 +1,9 @@ import { SSMProvider, DEFAULT_PROVIDERS } from './SSMProvider'; -import type { SSMGetMultipleOptionsInterface } from '../types/SSMProvider'; +import type { + SSMGetMultipleOptions, + SSMGetMultipleOptionsUnion, + SSMGetMultipleOutput, +} from '../types/SSMProvider'; /** * ## Intro @@ -134,18 +138,20 @@ import type { SSMGetMultipleOptionsInterface } from '../types/SSMProvider'; * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). * * @param {string} path - The path of the parameters to retrieve - * @param {SSMGetMultipleOptionsInterface} options - Options to configure the provider + * @param {SSMGetMultipleOptions} options - Options to configure the provider * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ -const getParameters = ( +const getParameters = ( path: string, - options?: SSMGetMultipleOptionsInterface -): Promise> => { + options?: O & SSMGetMultipleOptions +): Promise | undefined> => { if (!DEFAULT_PROVIDERS.hasOwnProperty('ssm')) { DEFAULT_PROVIDERS.ssm = new SSMProvider(); } - return (DEFAULT_PROVIDERS.ssm as SSMProvider).getMultiple(path, options); + return ( + DEFAULT_PROVIDERS.ssm as SSMProvider + ).getMultiple(path, options) as Promise | undefined>; }; export { diff --git a/packages/parameters/src/ssm/getParametersByName.ts b/packages/parameters/src/ssm/getParametersByName.ts index 5efd333f5f..f7532206bd 100644 --- a/packages/parameters/src/ssm/getParametersByName.ts +++ b/packages/parameters/src/ssm/getParametersByName.ts @@ -1,6 +1,7 @@ import { SSMProvider, DEFAULT_PROVIDERS } from './SSMProvider'; import type { - SSMGetParametersByNameOptionsInterface + SSMGetParametersByNameOptionsInterface, + SSMGetParametersByNameOutput, } from '../types/SSMProvider'; /** @@ -160,15 +161,17 @@ import type { * @param {SSMGetParametersByNameOptionsInterface} options - Options to configure the provider * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ -const getParametersByName = ( +const getParametersByName = ( parameters: Record, options?: SSMGetParametersByNameOptionsInterface -): Promise & { _errors?: string[] }> => { +): Promise> => { if (!DEFAULT_PROVIDERS.hasOwnProperty('ssm')) { DEFAULT_PROVIDERS.ssm = new SSMProvider(); } - return (DEFAULT_PROVIDERS.ssm as SSMProvider).getParametersByName(parameters, options); + return ( + DEFAULT_PROVIDERS.ssm as SSMProvider + ).getParametersByName(parameters, options) as Promise>; }; export { diff --git a/packages/parameters/src/types/BaseProvider.ts b/packages/parameters/src/types/BaseProvider.ts index c0e5a08c3d..f2a6a79a9b 100644 --- a/packages/parameters/src/types/BaseProvider.ts +++ b/packages/parameters/src/types/BaseProvider.ts @@ -25,9 +25,9 @@ interface GetOptionsInterface { */ sdkOptions?: unknown /** - * Transform to be applied, can be 'json', 'binary', or 'auto'. + * Transform to be applied, can be `json` or `binary`. */ - transform?: TransformOptions + transform?: Omit } /** @@ -40,6 +40,10 @@ interface GetOptionsInterface { * @property {boolean} throwOnTransformError - Whether to throw an error if a value cannot be transformed. */ interface GetMultipleOptionsInterface extends GetOptionsInterface { + /** + * Transform to be applied, can be `json`, `binary`, or `auto`. + */ + transform?: TransformOptions /** * Whether to throw an error if a value cannot be transformed. */ diff --git a/packages/parameters/src/types/SSMProvider.ts b/packages/parameters/src/types/SSMProvider.ts index 36d5f85cb3..9a4c789978 100644 --- a/packages/parameters/src/types/SSMProvider.ts +++ b/packages/parameters/src/types/SSMProvider.ts @@ -50,15 +50,46 @@ type SSMProviderOptions = SSMProviderOptionsWithClientConfig | SSMProviderOption * @extends {GetOptionsInterface} * @property {number} maxAge - Maximum age of the value in the cache, in seconds. * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. - * @property {GetItemCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. + * @property {GetItemCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. Supports all options from `GetParameterCommandInput`. * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. - * @property {boolean} decrypt - If true, the parameter will be decrypted. + * @property {boolean} decrypt - If true, the parameter will be decrypted. Defaults to `false`. */ -interface SSMGetOptionsInterface extends GetOptionsInterface { +interface SSMGetOptions extends GetOptionsInterface { + /** + * If true, the parameter will be decrypted. Defaults to `false`. + */ decrypt?: boolean + /** + * Additional options to pass to the AWS SDK v3 client. Supports all options from `GetParameterCommandInput`. + */ sdkOptions?: Partial + + transform?: Exclude +} + +interface SSMGetOptionsTransformJson extends SSMGetOptions { + transform: 'json' +} + +interface SSMGetOptionsTransformBinary extends SSMGetOptions { + transform: 'binary' } +interface SSMGetOptionsTransformNone extends SSMGetOptions { + transform?: never +} + +/** + * Generic output type for the SSMProvider get method. + */ +type SSMGetOutput = + undefined extends T ? + undefined extends O ? string : + O extends SSMGetOptionsTransformNone | SSMGetOptionsTransformBinary ? string : + O extends SSMGetOptionsTransformJson ? Record : + never + : T; + /** * Options for the SSMProvider getMultiple method. * @@ -72,13 +103,60 @@ interface SSMGetOptionsInterface extends GetOptionsInterface { * @property {boolean} recursive - If true, the parameter will be fetched recursively. * @property {boolean} throwOnTransformError - If true, the method will throw an error if the transform fails. */ -interface SSMGetMultipleOptionsInterface extends GetMultipleOptionsInterface { +interface SSMGetMultipleOptions extends GetMultipleOptionsInterface { + /** + * Additional options to pass to the AWS SDK v3 client. Supports all options from `GetParametersByPathCommandInput`. + */ sdkOptions?: Partial + /** + * If true, the parameters will be decrypted. Defaults to `false`. + */ decrypt?: boolean + /** + * If true, the parameters will be fetched recursively. Defaults to `false`. + */ recursive?: boolean + /** + * If true, the method will throw an error if the transform fails. + */ throwOnTransformError?: boolean } +interface SSMGetMultipleOptionsTransformJson extends SSMGetMultipleOptions { + transform: 'json' +} + +interface SSMGetMultipleOptionsTransformBinary extends SSMGetMultipleOptions { + transform: 'binary' +} + +interface SSMGetMultipleOptionsTransformAuto extends SSMGetMultipleOptions { + transform: 'auto' +} + +interface SSMGetMultipleOptionsTransformNone extends SSMGetMultipleOptions { + transform?: never +} + +type SSMGetMultipleOptionsUnion = + SSMGetMultipleOptionsTransformJson | + SSMGetMultipleOptionsTransformBinary | + SSMGetMultipleOptionsTransformAuto | + SSMGetMultipleOptionsTransformNone | + undefined; + +/** + * Generic output type for the SSMProvider getMultiple method. + */ +type SSMGetMultipleOutput = + undefined extends T ? + undefined extends O ? Record : + O extends SSMGetMultipleOptionsTransformNone | SSMGetMultipleOptionsTransformBinary ? Record : + O extends SSMGetMultipleOptionsTransformAuto ? Record : + O extends SSMGetMultipleOptionsTransformJson ? Record> : + never + : Record; + /** * Options for the SSMProvider getParametersByName method. * @@ -92,7 +170,7 @@ interface SSMGetParametersByNameOptionsInterface { maxAge?: number throwOnError?: boolean decrypt?: boolean - transform?: TransformOptions + transform?: Exclude } /** @@ -119,12 +197,21 @@ type SSMGetParametersByNameFromCacheOutputType = { toFetch: Record }; +type SSMGetParametersByNameOutput = + undefined extends T ? + Record & { _errors?: string[] } : + Record & { _errors?: string[] }; + export type { SSMProviderOptions, - SSMGetOptionsInterface, - SSMGetMultipleOptionsInterface, + SSMGetOptions, + SSMGetOutput, + SSMGetMultipleOptions, + SSMGetMultipleOptionsUnion, + SSMGetMultipleOutput, SSMGetParametersByNameOptionsInterface, SSMSplitBatchAndDecryptParametersOutputType, SSMGetParametersByNameOutputInterface, SSMGetParametersByNameFromCacheOutputType, + SSMGetParametersByNameOutput, }; \ No newline at end of file diff --git a/packages/parameters/tests/unit/getParameter.test.ts b/packages/parameters/tests/unit/getParameter.test.ts index f13f993cbf..b7bf839f57 100644 --- a/packages/parameters/tests/unit/getParameter.test.ts +++ b/packages/parameters/tests/unit/getParameter.test.ts @@ -9,6 +9,10 @@ import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm'; import { mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; +/** + * Note that the following tests include type annotations on the results of each call. This is to ensure that the + * generic types defined in the utility are working as expected. If they are not, the tests will fail to compile. + */ describe('Function: getParameter', () => { beforeEach(() => { @@ -27,7 +31,7 @@ describe('Function: getParameter', () => { }); // Act - const value = await getParameter(parameterName); + const value: string | undefined = await getParameter(parameterName); // Assess expect(client).toReceiveCommandWith(GetParameterCommand, { @@ -36,7 +40,7 @@ describe('Function: getParameter', () => { expect(value).toBe(parameterValue); }); - + test('when called and a default provider exists, it uses it and returns the value', async () => { // Prepare @@ -51,7 +55,7 @@ describe('Function: getParameter', () => { }); // Act - const value = await getParameter(parameterName); + const value: string | undefined = await getParameter(parameterName); // Assess expect(client).toReceiveCommandWith(GetParameterCommand, { @@ -62,4 +66,52 @@ describe('Function: getParameter', () => { }); + test('when called and transform `JSON` is specified, it returns an object with correct type', async () => { + + // Prepare + const provider = new SSMProvider(); + DEFAULT_PROVIDERS.ssm = provider; + const parameterName = 'foo'; + const parameterValue = JSON.stringify({ hello: 'world' }); + const client = mockClient(SSMClient).on(GetParameterCommand).resolves({ + Parameter: { + Value: parameterValue, + }, + }); + + // Act + const value: Record | undefined = await getParameter(parameterName, { transform: 'json' }); + + // Assess + expect(client).toReceiveCommandWith(GetParameterCommand, { + Name: parameterName, + }); + expect(value).toStrictEqual(JSON.parse(parameterValue)); + + }); + + 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 SSMProvider(); + DEFAULT_PROVIDERS.ssm = provider; + const parameterName = 'foo'; + const parameterValue = JSON.stringify(5); + const client = mockClient(SSMClient).on(GetParameterCommand).resolves({ + Parameter: { + Value: parameterValue, + }, + }); + + // Act + const value: number | undefined = await getParameter(parameterName, { transform: 'json' }); + + // Assess + expect(client).toReceiveCommandWith(GetParameterCommand, { + Name: parameterName, + }); + expect(value).toBe(JSON.parse(parameterValue)); + + }); + }); \ No newline at end of file diff --git a/packages/parameters/tests/unit/getParameters.test.ts b/packages/parameters/tests/unit/getParameters.test.ts index 8f9b68f6bd..2025c0ae7f 100644 --- a/packages/parameters/tests/unit/getParameters.test.ts +++ b/packages/parameters/tests/unit/getParameters.test.ts @@ -9,6 +9,10 @@ import { SSMClient, GetParametersByPathCommand } from '@aws-sdk/client-ssm'; import { mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; +/** + * Note that the following tests include type annotations on the results of each call. This is to ensure that the + * generic types defined in the utility are working as expected. If they are not, the tests will fail to compile. + */ describe('Function: getParameters', () => { beforeEach(() => { @@ -28,7 +32,7 @@ describe('Function: getParameters', () => { }); // Act - const parameters = await getParameters(parameterPath); + const parameters: Record | undefined = await getParameters(parameterPath); // Assess expect(client).toReceiveCommandWith(GetParametersByPathCommand, { @@ -39,7 +43,7 @@ describe('Function: getParameters', () => { }); }); - + test('when called and a default provider exists, it uses it and returns the value', async () => { // Prepare @@ -55,7 +59,7 @@ describe('Function: getParameters', () => { }); // Act - const parameters = await getParameters(parameterPath); + const parameters: Record | undefined = await getParameters(parameterPath); // Assess expect(client).toReceiveCommandWith(GetParametersByPathCommand, { @@ -68,4 +72,60 @@ describe('Function: getParameters', () => { }); + test('when called and transform `JSON` is specified, it returns an object with correct type', async () => { + + // Prepare + const provider = new SSMProvider(); + DEFAULT_PROVIDERS.ssm = provider; + const parameterPath = '/foo'; + const parameterValue = JSON.stringify({ hello: 'world' }); + const client = mockClient(SSMClient).on(GetParametersByPathCommand).resolves({ + Parameters: [{ + Name: '/foo/bar', + Value: parameterValue, + }], + }); + + // Act + const parameters: Record> | undefined = await getParameters(parameterPath); + + // Assess + expect(client).toReceiveCommandWith(GetParametersByPathCommand, { + Path: parameterPath, + }); + expect(parameters).toStrictEqual({ + 'bar': parameterValue, + }); + expect(DEFAULT_PROVIDERS.ssm).toBe(provider); + + }); + + 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 SSMProvider(); + DEFAULT_PROVIDERS.ssm = provider; + const parameterPath = '/foo'; + const parameterValue = JSON.stringify(5); + const client = mockClient(SSMClient).on(GetParametersByPathCommand).resolves({ + Parameters: [{ + Name: '/foo/bar', + Value: parameterValue, + }], + }); + + // Act + const parameters: Record> | undefined = await getParameters>(parameterPath); + + // Assess + expect(client).toReceiveCommandWith(GetParametersByPathCommand, { + Path: parameterPath, + }); + expect(parameters).toStrictEqual({ + 'bar': parameterValue, + }); + expect(DEFAULT_PROVIDERS.ssm).toBe(provider); + + }); + }); \ No newline at end of file