Skip to content

Commit 63eeab4

Browse files
authored
refactor(parameters): BaseProvider support for Uint8Array (#1205)
* refactor: added Uint8Array to BaseProvider * chore: manually bumped utility version
1 parent a09e4df commit 63eeab4

File tree

6 files changed

+81
-58
lines changed

6 files changed

+81
-58
lines changed

Diff for: package-lock.json

+2-22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: packages/parameters/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@aws-lambda-powertools/parameters",
3-
"version": "1.4.1",
3+
"version": "1.5.0",
44
"description": "The parameters package for the AWS Lambda Powertools for TypeScript library",
55
"author": {
66
"name": "Amazon Web Services",
@@ -42,7 +42,7 @@
4242
"url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues"
4343
},
4444
"dependencies": {
45-
"@aws-sdk/util-base64-node": "^3.209.0"
45+
"@aws-sdk/util-base64": "^3.208.0"
4646
},
4747
"keywords": [
4848
"aws",

Diff for: packages/parameters/src/BaseProvider.ts

+33-22
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1-
import { fromBase64 } from '@aws-sdk/util-base64-node';
1+
import { fromBase64 } from '@aws-sdk/util-base64';
22
import { GetOptions } from './GetOptions';
33
import { GetMultipleOptions } from './GetMultipleOptions';
44
import { ExpirableValue } from './ExpirableValue';
55
import { TRANSFORM_METHOD_BINARY, TRANSFORM_METHOD_JSON } from './constants';
66
import { GetParameterError, TransformParameterError } from './Exceptions';
77
import type { BaseProviderInterface, GetMultipleOptionsInterface, GetOptionsInterface, TransformOptions } from './types';
88

9+
// These providers are dinamycally intialized on first use of the helper functions
10+
const DEFAULT_PROVIDERS: Record<string, BaseProvider> = {};
11+
912
abstract class BaseProvider implements BaseProviderInterface {
1013
protected store: Map<string, ExpirableValue>;
1114

12-
public constructor () {
15+
public constructor() {
1316
this.store = new Map();
1417
}
1518

16-
public addToCache(key: string, value: string | Record<string, unknown>, maxAge: number): void {
19+
public addToCache(key: string, value: string | Uint8Array | Record<string, unknown>, maxAge: number): void {
1720
if (maxAge <= 0) return;
1821

1922
this.store.set(key, new ExpirableValue(value, maxAge));
@@ -22,7 +25,7 @@ abstract class BaseProvider implements BaseProviderInterface {
2225
public clearCache(): void {
2326
this.store.clear();
2427
}
25-
28+
2629
/**
2730
* Retrieve a parameter value or return the cached value
2831
*
@@ -37,7 +40,7 @@ abstract class BaseProvider implements BaseProviderInterface {
3740
* @param {string} name - Parameter name
3841
* @param {GetOptionsInterface} options - Options to configure maximum age, trasformation, AWS SDK options, or force fetch
3942
*/
40-
public async get(name: string, options?: GetOptionsInterface): Promise<undefined | string | Record<string, unknown>> {
43+
public async get(name: string, options?: GetOptionsInterface): Promise<undefined | string | Uint8Array | Record<string, unknown>> {
4144
const configs = new GetOptions(options);
4245
const key = [ name, configs.transform ].toString();
4346

@@ -49,7 +52,7 @@ abstract class BaseProvider implements BaseProviderInterface {
4952

5053
let value;
5154
try {
52-
value = await this._get(name, options?.sdkOptions);
55+
value = await this._get(name, options);
5356
} catch (error) {
5457
throw new GetParameterError((error as Error).message);
5558
}
@@ -76,9 +79,9 @@ abstract class BaseProvider implements BaseProviderInterface {
7679
return this.store.get(key)!.value as Record<string, unknown>;
7780
}
7881

79-
let values: Record<string, unknown> = {};
82+
let values = {};
8083
try {
81-
values = await this._getMultiple(path, options?.sdkOptions);
84+
values = await this._getMultiple(path, options);
8285
} catch (error) {
8386
throw new GetParameterError((error as Error).message);
8487
}
@@ -99,11 +102,17 @@ abstract class BaseProvider implements BaseProviderInterface {
99102
* Retrieve parameter value from the underlying parameter store
100103
*
101104
* @param {string} name - Parameter name
102-
* @param {unknown} sdkOptions - Options to pass to the underlying AWS SDK
105+
* @param {unknown} options - Options to pass to the underlying implemented method
103106
*/
104-
protected abstract _get(name: string, sdkOptions?: unknown): Promise<string | undefined>;
107+
protected abstract _get(name: string, options?: unknown): Promise<string | Uint8Array | undefined>;
105108

106-
protected abstract _getMultiple(path: string, sdkOptions?: unknown): Promise<Record<string, string|undefined>>;
109+
/**
110+
* Retrieve multiple parameter values from the underlying parameter store
111+
*
112+
* @param {string} path - Parameter name
113+
* @param {unknown} options - Options to pass to the underlying implementated method
114+
*/
115+
protected abstract _getMultiple(path: string, options?: unknown): Promise<Record<string, string | undefined>>;
107116

108117
/**
109118
* Check whether a key has expired in the cache or not
@@ -115,42 +124,43 @@ abstract class BaseProvider implements BaseProviderInterface {
115124
private hasKeyExpiredInCache(key: string): boolean {
116125
const value = this.store.get(key);
117126
if (value) return value.isExpired();
118-
127+
119128
return true;
120129
}
121130

122131
}
123132

124-
// TODO: revisit `value` type once we are clearer on the types returned by the various SDKs
125-
const transformValue = (value: unknown, transform: TransformOptions, throwOnTransformError: boolean, key: string = ''): string | Record<string, unknown> | undefined => {
133+
const transformValue = (value: string | Uint8Array | undefined, transform: TransformOptions, throwOnTransformError: boolean, key: string = ''): string | Record<string, unknown> | undefined => {
126134
try {
127135
const normalizedTransform = transform.toLowerCase();
128136
if (
129137
(normalizedTransform === TRANSFORM_METHOD_JSON ||
130-
(normalizedTransform === 'auto' && key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_JSON}`))) &&
138+
(normalizedTransform === 'auto' && key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_JSON}`))) &&
131139
typeof value === 'string'
132140
) {
133141
return JSON.parse(value) as Record<string, unknown>;
134142
} else if (
135143
(normalizedTransform === TRANSFORM_METHOD_BINARY ||
136-
(normalizedTransform === 'auto' && key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_BINARY}`))) &&
137-
typeof value === 'string'
144+
(normalizedTransform === 'auto' && key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_BINARY}`)))
138145
) {
139-
return new TextDecoder('utf-8').decode(fromBase64(value));
146+
if (typeof value === 'string') {
147+
return new TextDecoder('utf-8').decode(fromBase64(value));
148+
} else {
149+
return new TextDecoder('utf-8').decode(value);
150+
}
140151
} else {
141-
// TODO: revisit this type once we are clearer on types returned by SDKs
142152
return value as string;
143153
}
144154
} catch (error) {
145155
if (throwOnTransformError)
146156
throw new TransformParameterError(transform, (error as Error).message);
147-
157+
148158
return;
149159
}
150160
};
151161

152-
const transformValues = (value: Record<string, unknown>, transform: TransformOptions, throwOnTransformError: boolean): Record<string, unknown> => {
153-
const transformedValues: Record<string, unknown> = {};
162+
const transformValues = (value: Record<string, string | undefined>, transform: TransformOptions, throwOnTransformError: boolean): Record<string, string | Record<string, unknown> | undefined> => {
163+
const transformedValues: Record<string, string | Record<string, unknown> | undefined> = {};
154164
for (const [ entryKey, entryValue ] of Object.entries(value)) {
155165
try {
156166
transformedValues[entryKey] = transformValue(entryValue, transform, throwOnTransformError, entryKey);
@@ -167,4 +177,5 @@ export {
167177
BaseProvider,
168178
ExpirableValue,
169179
transformValue,
180+
DEFAULT_PROVIDERS,
170181
};

Diff for: packages/parameters/src/ExpirableValue.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import type { ExpirableValueInterface } from './types';
22

33
class ExpirableValue implements ExpirableValueInterface {
44
public ttl: number;
5-
public value: string | Record<string, unknown>;
5+
public value: string | Uint8Array | Record<string, unknown>;
66

7-
public constructor(value: string | Record<string, unknown>, maxAge: number) {
7+
public constructor(value: string | Uint8Array | Record<string, unknown>, maxAge: number) {
88
this.value = value;
99
const timeNow = new Date();
1010
this.ttl = timeNow.setSeconds(timeNow.getSeconds() + maxAge);
@@ -15,6 +15,6 @@ class ExpirableValue implements ExpirableValueInterface {
1515
}
1616
}
1717

18-
export {
18+
export {
1919
ExpirableValue
2020
};

Diff for: packages/parameters/src/types/BaseProvider.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ interface GetMultipleOptionsInterface {
1616
}
1717

1818
interface ExpirableValueInterface {
19-
value: string | Record<string, unknown>
19+
value: string | Uint8Array | Record<string, unknown>
2020
ttl: number
2121
}
2222

2323
interface BaseProviderInterface {
24-
get(name: string, options?: GetOptionsInterface): Promise<undefined | string | Record<string, unknown>>
24+
get(name: string, options?: GetOptionsInterface): Promise<undefined | string | Uint8Array | Record<string, unknown>>
2525
getMultiple(path: string, options?: GetMultipleOptionsInterface): Promise<void | Record<string, unknown>>
2626
}
2727

Diff for: packages/parameters/tests/unit/BaseProvider.test.ts

+39-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import { BaseProvider, ExpirableValue, GetParameterError, TransformParameterError } from '../../src';
8-
import { toBase64 } from '@aws-sdk/util-base64-node';
8+
import { toBase64 } from '@aws-sdk/util-base64';
99

1010
const encoder = new TextEncoder();
1111

@@ -76,7 +76,7 @@ describe('Class: BaseProvider', () => {
7676
// Prepare
7777
const provider = new TestProvider();
7878

79-
// Act / Assess
79+
// Act & Assess
8080
await expect(provider.get('my-parameter')).rejects.toThrowError(GetParameterError);
8181

8282
});
@@ -154,7 +154,7 @@ describe('Class: BaseProvider', () => {
154154
const provider = new TestProvider();
155155
jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData)));
156156

157-
// Act / Assess
157+
// Act & Assess
158158
await expect(provider.get('my-parameter', { transform: 'json' })).rejects.toThrowError(TransformParameterError);
159159

160160
});
@@ -181,10 +181,42 @@ describe('Class: BaseProvider', () => {
181181
const provider = new TestProvider();
182182
jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData)));
183183

184-
// Act / Assess
184+
// Act & Assess
185185
await expect(provider.get('my-parameter', { transform: 'binary' })).rejects.toThrowError(TransformParameterError);
186186

187187
});
188+
189+
test('when called with no transform, and the value is a valid binary, it returns the binary as-is', async () => {
190+
191+
// Prepare
192+
const mockData = encoder.encode('my-value');
193+
const provider = new TestProvider();
194+
jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData as unknown as string)));
195+
196+
// Act
197+
const value = await provider.get('my-parameter');
198+
199+
// Assess
200+
expect(value).toBeInstanceOf(Uint8Array);
201+
expect(value).toEqual(mockData);
202+
203+
});
204+
205+
test('when called with a binary transform, and the value is a valid binary, it returns the decoded value', async () => {
206+
207+
// Prepare
208+
const mockData = encoder.encode('my-value');
209+
const provider = new TestProvider();
210+
jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData as unknown as string)));
211+
212+
// Act
213+
const value = await provider.get('my-parameter', { transform: 'binary' });
214+
215+
// Assess
216+
expect(typeof value).toBe('string');
217+
expect(value).toEqual('my-value');
218+
219+
});
188220

189221
});
190222

@@ -195,7 +227,7 @@ describe('Class: BaseProvider', () => {
195227
const provider = new TestProvider();
196228
jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((_resolve, reject) => reject(new Error('Some error.'))));
197229

198-
// Act / Assess
230+
// Act & Assess
199231
await expect(provider.getMultiple('my-parameter')).rejects.toThrowError(GetParameterError);
200232

201233
});
@@ -267,7 +299,7 @@ describe('Class: BaseProvider', () => {
267299
const provider = new TestProvider();
268300
jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData)));
269301

270-
// Act / Assess
302+
// Act & Assess
271303
await expect(provider.getMultiple('my-path', { transform: 'json', throwOnTransformError: true })).rejects.toThrowError(TransformParameterError);
272304

273305
});
@@ -316,7 +348,7 @@ describe('Class: BaseProvider', () => {
316348
const provider = new TestProvider();
317349
jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData)));
318350

319-
// Act / Assess
351+
// Act & Assess
320352
await expect(provider.getMultiple('my-path', { transform: 'binary', throwOnTransformError: true })).rejects.toThrowError(TransformParameterError);
321353

322354
});

0 commit comments

Comments
 (0)