Skip to content

Commit 8039e02

Browse files
merlinnotkevinajian
authored andcommitted
Allow specifying failure policies (#482)
* Define an interface for FailurePolicy * Extract functions config to avoid dependency cycles, assign failure policy to triggers * Add a changelog entry * Update dependencies, minor version bump * Reformat * Fix a typo: allows -> allow * Avoid abbreviations to improve readability * Rename remaining Opts to Options * Add tests for specifying failure policy * Reformat * Change format of an entry in the changelog * Revert version bump * Extract configuration to break dependency cycle * Add comments to Schedule and ScheduleRetryConfig * Stricten regions definition * Fix tests broken due to strict typings * More strict types for regions - reverse order * Reformat * Remove unused import * Conform with npm formatting * Reintroduce unused variable to minimize diff size * Import lodash using _ exclusively * Kepp @hidden tags in one line if no other JSDoc is present * Fix a typo - sentence ending with a comma * Wrap JSDoc as specified in Google JavaScript Style Guide * Move memory lookup table to functions-configuration and stricten type definition * Move default failure policy to functions-configuration * Separate standarization of options from construction of the options object * Rephrase failure policy description * Rephrase description of memory and timeoutSeconds options * Simplify tests for invalid failure policies * Align public description of failure policies with a documentation of its interface * Add a missing dot in the Changelog * Minor stylistic changes to the documentation * Make a test case more understandable * Reformat
1 parent edcb35d commit 8039e02

File tree

6 files changed

+212
-82
lines changed

6 files changed

+212
-82
lines changed

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
feature - Allow specifying retry policies for event triggered functions.

spec/function-builder.spec.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,29 @@ describe('FunctionBuilder', () => {
7979
it('should allow valid runtime options to be set', () => {
8080
const fn = functions
8181
.runWith({
82-
timeoutSeconds: 90,
82+
failurePolicy: { retry: {} },
8383
memory: '256MB',
84+
timeoutSeconds: 90,
8485
})
8586
.auth.user()
8687
.onCreate((user) => user);
8788

8889
expect(fn.__trigger.availableMemoryMb).to.deep.equal(256);
8990
expect(fn.__trigger.timeout).to.deep.equal('90s');
91+
expect(fn.__trigger.failurePolicy).to.deep.equal({ retry: {} });
92+
});
93+
94+
it("should apply a default failure policy if it's aliased with `true`", () => {
95+
const fn = functions
96+
.runWith({
97+
failurePolicy: true,
98+
memory: '256MB',
99+
timeoutSeconds: 90,
100+
})
101+
.auth.user()
102+
.onCreate((user) => user);
103+
104+
expect(fn.__trigger.failurePolicy).to.deep.equal({ retry: {} });
90105
});
91106

92107
it('should allow both supported region and valid runtime options to be set', () => {
@@ -132,7 +147,26 @@ describe('FunctionBuilder', () => {
132147
functions
133148
.region('asia-northeast1')
134149
.runWith({ timeoutSeconds: 600, memory: '256MB' });
135-
}).to.throw(Error, 'TimeoutSeconds');
150+
}).to.throw(Error, 'RuntimeOptions.timeoutSeconds');
151+
});
152+
153+
it('should throw an error if user chooses a failurePolicy which is neither an object nor a boolean', () => {
154+
expect(() =>
155+
functions.runWith({
156+
failurePolicy: (1234 as unknown) as functions.RuntimeOptions['failurePolicy'],
157+
})
158+
).to.throw(
159+
Error,
160+
'RuntimeOptions.failurePolicy must be a boolean or an object'
161+
);
162+
});
163+
164+
it('should throw an error if user chooses a failurePolicy.retry which is not an object', () => {
165+
expect(() =>
166+
functions.runWith({
167+
failurePolicy: { retry: (1234 as unknown) as object },
168+
})
169+
).to.throw(Error, 'RuntimeOptions.failurePolicy.retry');
136170
});
137171

138172
it('should throw an error if user chooses an invalid memory allocation', () => {
@@ -154,13 +188,13 @@ describe('FunctionBuilder', () => {
154188
return functions.runWith({
155189
timeoutSeconds: 1000000,
156190
} as any);
157-
}).to.throw(Error, 'TimeoutSeconds');
191+
}).to.throw(Error, 'RuntimeOptions.timeoutSeconds');
158192

159193
expect(() => {
160194
return functions.region('asia-east2').runWith({
161195
timeoutSeconds: 1000000,
162196
} as any);
163-
}).to.throw(Error, 'TimeoutSeconds');
197+
}).to.throw(Error, 'RuntimeOptions.timeoutSeconds');
164198
});
165199

166200
it('should throw an error if user chooses an invalid region', () => {

spec/providers/auth.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,7 @@ describe('Auth Functions', () => {
197197
});
198198

199199
describe('#onDelete', () => {
200-
const cloudFunctionDelete: CloudFunction<
201-
firebase.auth.UserRecord
202-
> = functions.handler.auth.user.onDelete(
200+
const cloudFunctionDelete: CloudFunction<firebase.auth.UserRecord> = functions.handler.auth.user.onDelete(
203201
(data: firebase.auth.UserRecord) => data
204202
);
205203

src/cloud-functions.ts

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@
2222

2323
import { Request, Response } from 'express';
2424
import * as _ from 'lodash';
25-
import { DeploymentOptions, Schedule } from './function-configuration';
25+
import {
26+
DEFAULT_FAILURE_POLICY,
27+
DeploymentOptions,
28+
FailurePolicy,
29+
MEMORY_LOOKUP,
30+
Schedule,
31+
} from './function-configuration';
2632
export { Request, Response };
2733

2834
/** @hidden */
@@ -202,6 +208,7 @@ export namespace Change {
202208
if (json.fieldMask) {
203209
before = applyFieldMask(before, json.after, json.fieldMask);
204210
}
211+
205212
return Change.fromObjects(
206213
customizer(before || {}),
207214
customizer(json.after || {})
@@ -216,14 +223,16 @@ export namespace Change {
216223
) {
217224
const before = _.assign({}, after);
218225
const masks = fieldMask.split(',');
219-
_.forEach(masks, (mask) => {
226+
227+
masks.forEach((mask) => {
220228
const val = _.get(sparseBefore, mask);
221229
if (typeof val === 'undefined') {
222230
_.unset(before, mask);
223231
} else {
224232
_.set(before, mask, val);
225233
}
226234
});
235+
227236
return before;
228237
}
229238
}
@@ -253,6 +262,7 @@ export interface TriggerAnnotated {
253262
resource: string;
254263
service: string;
255264
};
265+
failurePolicy?: FailurePolicy;
256266
httpsTrigger?: {};
257267
labels?: { [key: string]: string };
258268
regions?: string[];
@@ -312,6 +322,40 @@ export interface MakeCloudFunctionArgs<EventData> {
312322
triggerResource: () => string;
313323
}
314324

325+
/** @hidden */
326+
export function optionsToTrigger({
327+
failurePolicy: failurePolicyOrAlias,
328+
memory,
329+
regions,
330+
schedule,
331+
timeoutSeconds,
332+
}: DeploymentOptions): TriggerAnnotated['__trigger'] {
333+
/*
334+
* FailurePolicy can be aliased with a boolean value in the public API.
335+
* Convert aliases `true` and `false` to a standardized interface.
336+
*/
337+
const failurePolicy: FailurePolicy | undefined =
338+
failurePolicyOrAlias === false
339+
? undefined
340+
: failurePolicyOrAlias === true
341+
? DEFAULT_FAILURE_POLICY
342+
: failurePolicyOrAlias;
343+
344+
const availableMemoryMb: number | undefined =
345+
memory === undefined ? undefined : MEMORY_LOOKUP[memory];
346+
347+
const timeout: string | undefined =
348+
timeoutSeconds === undefined ? undefined : `${timeoutSeconds}s`;
349+
350+
return {
351+
...(failurePolicy === undefined ? {} : { failurePolicy }),
352+
...(availableMemoryMb === undefined ? {} : { availableMemoryMb }),
353+
...(regions === undefined ? {} : { regions }),
354+
...(schedule === undefined ? {} : { schedule }),
355+
...(timeout === undefined ? {} : { timeout }),
356+
};
357+
}
358+
315359
/** @hidden */
316360
export function makeCloudFunction<EventData>({
317361
after = () => {},
@@ -463,28 +507,3 @@ function _detectAuthType(event: Event) {
463507
}
464508
return 'UNAUTHENTICATED';
465509
}
466-
467-
/** @hidden */
468-
export function optionsToTrigger(options: DeploymentOptions) {
469-
const trigger: any = {};
470-
if (options.regions) {
471-
trigger.regions = options.regions;
472-
}
473-
if (options.timeoutSeconds) {
474-
trigger.timeout = options.timeoutSeconds.toString() + 's';
475-
}
476-
if (options.memory) {
477-
const memoryLookup = {
478-
'128MB': 128,
479-
'256MB': 256,
480-
'512MB': 512,
481-
'1GB': 1024,
482-
'2GB': 2048,
483-
};
484-
trigger.availableMemoryMb = _.get(memoryLookup, options.memory);
485-
}
486-
if (options.schedule) {
487-
trigger.schedule = options.schedule;
488-
}
489-
return trigger;
490-
}

0 commit comments

Comments
 (0)