Skip to content

Commit d227f96

Browse files
authored
add validation for environment prop (#2290)
* add validation for environment prop * remove redundant length validation * PR feedback
1 parent 0cf5c26 commit d227f96

File tree

4 files changed

+173
-30
lines changed

4 files changed

+173
-30
lines changed

.changeset/shy-lions-smash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@aws-amplify/backend-function': patch
3+
---
4+
5+
change errors in FunctionFactory to AmplifyUserError

.changeset/weak-nails-change.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@aws-amplify/backend-function': patch
3+
---
4+
5+
add validation for environment prop

packages/backend-function/src/factory.test.ts

Lines changed: 122 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Runtime } from 'aws-cdk-lib/aws-lambda';
1919
import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam';
2020
import fsp from 'fs/promises';
2121
import path from 'node:path';
22+
import { AmplifyUserError } from '@aws-amplify/platform-core';
2223

2324
const createStackAndSetContext = (): Stack => {
2425
const app = new App();
@@ -207,9 +208,10 @@ void describe('AmplifyFunctionFactory', () => {
207208
entry: './test-assets/default-lambda/handler.ts',
208209
timeoutSeconds: 0,
209210
}).getInstance(getInstanceProps),
210-
new Error(
211-
'timeoutSeconds must be a whole number between 1 and 900 inclusive'
212-
)
211+
new AmplifyUserError('InvalidTimeoutError', {
212+
message: `Invalid function timeout of 0`,
213+
resolution: `timeoutSeconds must be a whole number between 1 and 900 inclusive`,
214+
})
213215
);
214216
});
215217

@@ -220,9 +222,10 @@ void describe('AmplifyFunctionFactory', () => {
220222
entry: './test-assets/default-lambda/handler.ts',
221223
timeoutSeconds: 901,
222224
}).getInstance(getInstanceProps),
223-
new Error(
224-
'timeoutSeconds must be a whole number between 1 and 900 inclusive'
225-
)
225+
new AmplifyUserError('InvalidTimeoutError', {
226+
message: `Invalid function timeout of 901`,
227+
resolution: `timeoutSeconds must be a whole number between 1 and 900 inclusive`,
228+
})
226229
);
227230
});
228231

@@ -233,9 +236,10 @@ void describe('AmplifyFunctionFactory', () => {
233236
entry: './test-assets/default-lambda/handler.ts',
234237
timeoutSeconds: 10.5,
235238
}).getInstance(getInstanceProps),
236-
new Error(
237-
'timeoutSeconds must be a whole number between 1 and 900 inclusive'
238-
)
239+
new AmplifyUserError('InvalidTimeoutError', {
240+
message: `Invalid function timeout of 10.5`,
241+
resolution: `timeoutSeconds must be a whole number between 1 and 900 inclusive`,
242+
})
239243
);
240244
});
241245
});
@@ -271,9 +275,10 @@ void describe('AmplifyFunctionFactory', () => {
271275
entry: './test-assets/default-lambda/handler.ts',
272276
memoryMB: 127,
273277
}).getInstance(getInstanceProps),
274-
new Error(
275-
'memoryMB must be a whole number between 128 and 10240 inclusive'
276-
)
278+
new AmplifyUserError('InvalidMemoryMBError', {
279+
message: `Invalid function memoryMB of 127`,
280+
resolution: `memoryMB must be a whole number between 128 and 10240 inclusive`,
281+
})
277282
);
278283
});
279284

@@ -284,9 +289,10 @@ void describe('AmplifyFunctionFactory', () => {
284289
entry: './test-assets/default-lambda/handler.ts',
285290
memoryMB: 10241,
286291
}).getInstance(getInstanceProps),
287-
new Error(
288-
'memoryMB must be a whole number between 128 and 10240 inclusive'
289-
)
292+
new AmplifyUserError('InvalidMemoryMBError', {
293+
message: `Invalid function memoryMB of 10241`,
294+
resolution: `memoryMB must be a whole number between 128 and 10240 inclusive`,
295+
})
290296
);
291297
});
292298

@@ -297,9 +303,103 @@ void describe('AmplifyFunctionFactory', () => {
297303
entry: './test-assets/default-lambda/handler.ts',
298304
memoryMB: 256.2,
299305
}).getInstance(getInstanceProps),
300-
new Error(
301-
'memoryMB must be a whole number between 128 and 10240 inclusive'
302-
)
306+
new AmplifyUserError('InvalidMemoryMBError', {
307+
message: `Invalid function memoryMB of 256.2`,
308+
resolution: `memoryMB must be a whole number between 128 and 10240 inclusive`,
309+
})
310+
);
311+
});
312+
});
313+
314+
void describe('environment property', () => {
315+
void it('sets valid environment', () => {
316+
const functionFactory = defineFunction({
317+
entry: './test-assets/default-lambda/handler.ts',
318+
name: 'myCoolLambda',
319+
environment: {
320+
TEST_ENV: 'testValue',
321+
},
322+
});
323+
const lambda = functionFactory.getInstance(getInstanceProps);
324+
const stack = lambda.stack;
325+
const template = Template.fromStack(stack);
326+
template.resourceCountIs('AWS::Lambda::Function', 1);
327+
template.hasResourceProperties('AWS::Lambda::Function', {
328+
Environment: {
329+
Variables: {
330+
TEST_ENV: 'testValue',
331+
},
332+
},
333+
});
334+
});
335+
336+
void it('sets default environment', () => {
337+
const functionFactory = defineFunction({
338+
entry: './test-assets/default-lambda/handler.ts',
339+
name: 'myCoolLambda',
340+
});
341+
const lambda = functionFactory.getInstance(getInstanceProps);
342+
const stack = lambda.stack;
343+
const template = Template.fromStack(stack);
344+
template.resourceCountIs('AWS::Lambda::Function', 1);
345+
template.hasResourceProperties('AWS::Lambda::Function', {
346+
Environment: {},
347+
});
348+
});
349+
350+
void it('throws when adding environment variables with invalid key', () => {
351+
assert.throws(
352+
() =>
353+
defineFunction({
354+
entry: './test-assets/default-lambda/handler.ts',
355+
name: 'myCoolLambda',
356+
environment: {
357+
'this.is.wrong': 'testValue',
358+
},
359+
}).getInstance(getInstanceProps),
360+
new AmplifyUserError('InvalidEnvironmentKeyError', {
361+
message: `Invalid function environment key(s): this.is.wrong`,
362+
resolution:
363+
'Environment keys must match [a-zA-Z]([a-zA-Z0-9_])+ and be at least 2 characters',
364+
})
365+
);
366+
});
367+
368+
void it('throws when adding environment variables with key less than 2 characters', () => {
369+
assert.throws(
370+
() =>
371+
defineFunction({
372+
entry: './test-assets/default-lambda/handler.ts',
373+
name: 'myCoolLambda',
374+
environment: {
375+
A: 'testValue',
376+
},
377+
}).getInstance(getInstanceProps),
378+
new AmplifyUserError('InvalidEnvironmentKeyError', {
379+
message: `Invalid function environment key(s): A`,
380+
resolution:
381+
'Environment keys must match [a-zA-Z]([a-zA-Z0-9_])+ and be at least 2 characters',
382+
})
383+
);
384+
});
385+
386+
void it('throws when multiple environment variables are invalid', () => {
387+
assert.throws(
388+
() =>
389+
defineFunction({
390+
entry: './test-assets/default-lambda/handler.ts',
391+
name: 'lambdaWithMultipleEnvVars',
392+
environment: {
393+
A: 'testValueA',
394+
TEST_ENV: 'envValue',
395+
'this.is.wrong': 'testValue',
396+
},
397+
}).getInstance(getInstanceProps),
398+
new AmplifyUserError('InvalidEnvironmentKeyError', {
399+
message: `Invalid function environment key(s): A, this.is.wrong`,
400+
resolution:
401+
'Environment keys must match [a-zA-Z]([a-zA-Z0-9_])+ and be at least 2 characters',
402+
})
303403
);
304404
});
305405
});
@@ -335,7 +435,10 @@ void describe('AmplifyFunctionFactory', () => {
335435
entry: './test-assets/default-lambda/handler.ts',
336436
runtime: 14 as NodeVersion,
337437
}).getInstance(getInstanceProps),
338-
new Error('runtime must be one of the following: 16, 18, 20, 22')
438+
new AmplifyUserError('InvalidRuntimeError', {
439+
message: `Invalid function runtime of 14`,
440+
resolution: 'runtime must be one of the following: 16, 18, 20, 22',
441+
})
339442
);
340443
});
341444

packages/backend-function/src/factory.ts

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ class FunctionFactory implements ConstructFactory<AmplifyFunction> {
232232
entry: this.resolveEntry(),
233233
timeoutSeconds: this.resolveTimeout(),
234234
memoryMB: this.resolveMemory(),
235-
environment: this.props.environment ?? {},
235+
environment: this.resolveEnvironment(),
236236
runtime: this.resolveRuntime(),
237237
schedule: this.resolveSchedule(),
238238
bundling: this.resolveBundling(),
@@ -294,9 +294,10 @@ class FunctionFactory implements ConstructFactory<AmplifyFunction> {
294294
timeoutMax
295295
)
296296
) {
297-
throw new Error(
298-
`timeoutSeconds must be a whole number between ${timeoutMin} and ${timeoutMax} inclusive`
299-
);
297+
throw new AmplifyUserError('InvalidTimeoutError', {
298+
message: `Invalid function timeout of ${this.props.timeoutSeconds}`,
299+
resolution: `timeoutSeconds must be a whole number between ${timeoutMin} and ${timeoutMax} inclusive`,
300+
});
300301
}
301302
return this.props.timeoutSeconds;
302303
};
@@ -311,13 +312,41 @@ class FunctionFactory implements ConstructFactory<AmplifyFunction> {
311312
if (
312313
!isWholeNumberBetweenInclusive(this.props.memoryMB, memoryMin, memoryMax)
313314
) {
314-
throw new Error(
315-
`memoryMB must be a whole number between ${memoryMin} and ${memoryMax} inclusive`
316-
);
315+
throw new AmplifyUserError('InvalidMemoryMBError', {
316+
message: `Invalid function memoryMB of ${this.props.memoryMB}`,
317+
resolution: `memoryMB must be a whole number between ${memoryMin} and ${memoryMax} inclusive`,
318+
});
317319
}
318320
return this.props.memoryMB;
319321
};
320322

323+
private resolveEnvironment = () => {
324+
if (this.props.environment === undefined) {
325+
return {};
326+
}
327+
328+
const invalidKeys: string[] = [];
329+
330+
Object.keys(this.props.environment).forEach((key) => {
331+
// validate using key pattern from https://docs.aws.amazon.com/lambda/latest/api/API_Environment.html
332+
if (!key.match(/^[a-zA-Z]([a-zA-Z0-9_])+$/)) {
333+
invalidKeys.push(key);
334+
}
335+
});
336+
337+
if (invalidKeys.length > 0) {
338+
throw new AmplifyUserError('InvalidEnvironmentKeyError', {
339+
message: `Invalid function environment key(s): ${invalidKeys.join(
340+
', '
341+
)}`,
342+
resolution:
343+
'Environment keys must match [a-zA-Z]([a-zA-Z0-9_])+ and be at least 2 characters',
344+
});
345+
}
346+
347+
return this.props.environment;
348+
};
349+
321350
private resolveRuntime = () => {
322351
const runtimeDefault = 18;
323352

@@ -327,11 +356,12 @@ class FunctionFactory implements ConstructFactory<AmplifyFunction> {
327356
}
328357

329358
if (!(this.props.runtime in nodeVersionMap)) {
330-
throw new Error(
331-
`runtime must be one of the following: ${Object.keys(
359+
throw new AmplifyUserError('InvalidRuntimeError', {
360+
message: `Invalid function runtime of ${this.props.runtime}`,
361+
resolution: `runtime must be one of the following: ${Object.keys(
332362
nodeVersionMap
333-
).join(', ')}`
334-
);
363+
).join(', ')}`,
364+
});
335365
}
336366

337367
return this.props.runtime;

0 commit comments

Comments
 (0)