Skip to content

Commit 424cfa5

Browse files
authored
Fix Firebase Functions v4 compatibility issues. (#175)
1. Replace all use of __trigger annotation to __endpoint 2. Correct import path for Expression class/type
1 parent d958110 commit 424cfa5

File tree

5 files changed

+68
-37
lines changed

5 files changed

+68
-37
lines changed

spec/main.spec.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ describe('main', () => {
3939
set(cloudFunction, 'run', (data, context) => {
4040
return { data, context };
4141
});
42-
set(cloudFunction, '__trigger', {
42+
set(cloudFunction, '__endpoint', {
4343
eventTrigger: {
44-
resource: 'ref/{wildcard}/nested/{anotherWildcard}',
44+
eventFilters: {
45+
resource: 'ref/{wildcard}/nested/{anotherWildcard}',
46+
},
4547
eventType: eventType || 'event',
46-
service: 'service',
48+
retry: false,
4749
},
4850
});
4951
return cloudFunction as functions.CloudFunction<any>;
@@ -57,7 +59,9 @@ describe('main', () => {
5759
it('should generate the appropriate context if no fields specified', () => {
5860
const context = wrap(constructBackgroundCF())('data').context;
5961
expect(typeof context.eventId).to.equal('string');
60-
expect(context.resource.service).to.equal('service');
62+
expect(context.resource.service).to.equal(
63+
'unknown-service.googleapis.com'
64+
);
6165
expect(
6266
/ref\/wildcard[1-9]\/nested\/anotherWildcard[1-9]/.test(
6367
context.resource.name
@@ -153,7 +157,7 @@ describe('main', () => {
153157
const cf = constructBackgroundCF(
154158
'google.firebase.database.ref.create'
155159
);
156-
cf.__trigger.eventTrigger.resource =
160+
cf.__endpoint.eventTrigger.eventFilters.resource =
157161
'companies/{company}/users/{user}';
158162
const wrapped = wrap(cf);
159163
const context = wrapped(
@@ -173,7 +177,7 @@ describe('main', () => {
173177

174178
it('should extract the appropriate params for Firestore function trigger', () => {
175179
const cf = constructBackgroundCF('google.firestore.document.create');
176-
cf.__trigger.eventTrigger.resource =
180+
cf.__endpoint.eventTrigger.eventFilters.resource =
177181
'databases/(default)/documents/companies/{company}/users/{user}';
178182
const wrapped = wrap(cf);
179183
const context = wrapped(
@@ -195,7 +199,7 @@ describe('main', () => {
195199
const cf = constructBackgroundCF(
196200
'google.firebase.database.ref.create'
197201
);
198-
cf.__trigger.eventTrigger.resource =
202+
cf.__endpoint.eventTrigger.eventFilters.resource =
199203
'companies/{company}/users/{user}';
200204
const wrapped = wrap(cf);
201205
const context = wrapped(
@@ -243,11 +247,8 @@ describe('main', () => {
243247
set(cloudFunction, 'run', (data, context) => {
244248
return { data, context };
245249
});
246-
set(cloudFunction, '__trigger', {
247-
labels: {
248-
'deployment-callable': 'true',
249-
},
250-
httpsTrigger: {},
250+
set(cloudFunction, '__endpoint', {
251+
callableTrigger: {},
251252
});
252253
wrappedCF = wrap(cloudFunction as functions.CloudFunction<any>);
253254
});

spec/v2.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
eventarc,
3434
https,
3535
} from 'firebase-functions/v2';
36-
import { defineString } from 'firebase-functions/v2/params';
36+
import { defineString } from 'firebase-functions/params';
3737
import { makeDataSnapshot } from '../src/providers/database';
3838

3939
describe('v2', () => {

src/cloudevent/mocks/database/helpers.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { CloudFunction, database } from 'firebase-functions/v2';
2-
import { Expression } from 'firebase-functions/v2/params';
32
import { DeepPartial } from '../../types';
43
import {
54
exampleDataSnapshot,

src/cloudevent/mocks/helpers.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
1-
import { CloudEvent, CloudFunction } from 'firebase-functions/v2';
2-
import { Expression } from 'firebase-functions/v2/params';
1+
import * as v1 from 'firebase-functions';
2+
import * as v2 from 'firebase-functions/v2';
3+
import { Expression } from 'firebase-functions/params';
34

45
export const APP_ID = '__APP_ID__';
56
export const PROJECT_ID = '42';
67
export const FILENAME = 'file_name';
78

8-
export function getEventType(cloudFunction: CloudFunction<any>): string {
9+
type CloudFunction = v1.CloudFunction<any> | v2.CloudFunction<any>;
10+
11+
export function getEventType(cloudFunction: CloudFunction): string {
912
return cloudFunction?.__endpoint?.eventTrigger?.eventType || '';
1013
}
1114

1215
export function getEventFilters(
13-
cloudFunction: CloudFunction<any>
16+
cloudFunction: CloudFunction
1417
): Record<string, string | Expression<string>> {
1518
return cloudFunction?.__endpoint?.eventTrigger?.eventFilters || {};
1619
}
1720

18-
export function getBaseCloudEvent<EventType extends CloudEvent<unknown>>(
19-
cloudFunction: CloudFunction<EventType>
21+
export function getBaseCloudEvent<EventType extends v2.CloudEvent<unknown>>(
22+
cloudFunction: v2.CloudFunction<EventType>
2023
): EventType {
2124
return {
2225
specversion: '1.0',

src/v1.ts

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ import {
3535
// @ts-ignore
3636
resetCache,
3737
} from 'firebase-functions';
38+
import { Expression } from 'firebase-functions/params';
39+
import {
40+
getEventFilters,
41+
getEventType,
42+
resolveStringExpression,
43+
} from './cloudevent/mocks/helpers';
3844

3945
/** Fields of the event context that can be overridden/customized. */
4046
export type EventContextOptions = {
@@ -118,13 +124,13 @@ export function wrapV1<T>(
118124
export function wrapV1<T>(
119125
cloudFunction: CloudFunction<T>
120126
): WrappedScheduledFunction | WrappedFunction<T, CloudFunction<T>> {
121-
if (!has(cloudFunction, '__trigger')) {
127+
if (!has(cloudFunction, '__endpoint')) {
122128
throw new Error(
123129
'Wrap can only be called on functions written with the firebase-functions SDK.'
124130
);
125131
}
126132

127-
if (get(cloudFunction, '__trigger.labels.deployment-scheduled') === 'true') {
133+
if (has(cloudFunction, '__endpoint.scheduleTrigger')) {
128134
const scheduledWrapped: WrappedScheduledFunction = (
129135
options: ContextOptions
130136
) => {
@@ -141,10 +147,7 @@ export function wrapV1<T>(
141147
return scheduledWrapped;
142148
}
143149

144-
if (
145-
has(cloudFunction, '__trigger.httpsTrigger') &&
146-
get(cloudFunction, '__trigger.labels.deployment-callable') !== 'true'
147-
) {
150+
if (has(cloudFunction, '__endpoint.httpsTrigger')) {
148151
throw new Error(
149152
'Wrap function is only available for `onCall` HTTP functions, not `onRequest`.'
150153
);
@@ -156,8 +159,7 @@ export function wrapV1<T>(
156159
);
157160
}
158161

159-
const isCallableFunction =
160-
get(cloudFunction, '__trigger.labels.deployment-callable') === 'true';
162+
const isCallableFunction = has(cloudFunction, '__endpoint.callableTrigger');
161163

162164
let wrapped: WrappedFunction<T, typeof cloudFunction> = (data, options) => {
163165
// Although in Typescript we require `options` some of our JS samples do not pass it.
@@ -199,11 +201,12 @@ export function wrapV1<T>(
199201

200202
/** @internal */
201203
export function _makeResourceName(
202-
triggerResource: string,
204+
triggerResource: string | Expression<string>,
203205
params = {}
204206
): string {
207+
const resource = resolveStringExpression(triggerResource);
205208
const wildcardRegex = new RegExp('{[^/{}]*}', 'g');
206-
let resourceName = triggerResource.replace(wildcardRegex, (wildcard) => {
209+
let resourceName = resource.replace(wildcardRegex, (wildcard) => {
207210
let wildcardNoBraces = wildcard.slice(1, -1); // .slice removes '{' and '}' from wildcard
208211
let sub = get(params, wildcardNoBraces);
209212
return sub || wildcardNoBraces + random(1, 9);
@@ -246,8 +249,8 @@ function _makeDefaultContext<T>(
246249
triggerData?: T
247250
): EventContext {
248251
let eventContextOptions = options as EventContextOptions;
249-
const eventResource = cloudFunction.__trigger.eventTrigger?.resource;
250-
const eventType = cloudFunction.__trigger.eventTrigger?.eventType;
252+
const eventType = getEventType(cloudFunction);
253+
const eventResource = getEventFilters(cloudFunction).resource;
251254

252255
const optionsParams = eventContextOptions.params ?? {};
253256
let triggerParams = {};
@@ -283,7 +286,7 @@ function _makeDefaultContext<T>(
283286
const defaultContext: EventContext = {
284287
eventId: _makeEventId(),
285288
resource: eventResource && {
286-
service: cloudFunction.__trigger.eventTrigger?.service,
289+
service: serviceFromEventType(eventType),
287290
name: _makeResourceName(eventResource, params),
288291
},
289292
eventType,
@@ -294,20 +297,22 @@ function _makeDefaultContext<T>(
294297
}
295298

296299
function _extractDatabaseParams(
297-
triggerResource: string,
300+
triggerResource: string | Expression<string>,
298301
data: database.DataSnapshot
299302
): EventContext['params'] {
303+
const resource = resolveStringExpression(triggerResource);
300304
const path = data.ref.toString().replace(data.ref.root.toString(), '');
301-
return _extractParams(triggerResource, path);
305+
return _extractParams(resource, path);
302306
}
303307

304308
function _extractFirestoreDocumentParams(
305-
triggerResource: string,
309+
triggerResource: string | Expression<string>,
306310
data: firestore.DocumentSnapshot
307311
): EventContext['params'] {
312+
const resource = resolveStringExpression(triggerResource);
308313
// Resource format: databases/(default)/documents/<path>
309314
return _extractParams(
310-
triggerResource.replace(/^databases\/[^\/]+\/documents\//, ''),
315+
resource.replace(/^databases\/[^\/]+\/documents\//, ''),
311316
data.ref.path
312317
);
313318
}
@@ -340,6 +345,29 @@ export function _extractParams(
340345
return params;
341346
}
342347

348+
function serviceFromEventType(eventType?: string): string {
349+
if (eventType) {
350+
const providerToService: Array<[string, string]> = [
351+
['google.analytics', 'app-measurement.com'],
352+
['google.firebase.auth', 'firebaseauth.googleapis.com'],
353+
['google.firebase.database', 'firebaseio.com'],
354+
['google.firestore', 'firestore.googleapis.com'],
355+
['google.pubsub', 'pubsub.googleapis.com'],
356+
['google.firebase.remoteconfig', 'firebaseremoteconfig.googleapis.com'],
357+
['google.storage', 'storage.googleapis.com'],
358+
['google.testing', 'testing.googleapis.com'],
359+
];
360+
361+
const match = providerToService.find(([provider]) => {
362+
eventType.includes(provider);
363+
});
364+
if (match) {
365+
return match[1];
366+
}
367+
}
368+
return 'unknown-service.googleapis.com';
369+
}
370+
343371
/** Make a Change object to be used as test data for Firestore and real time database onWrite and onUpdate functions. */
344372
export function makeChange<T>(before: T, after: T): Change<T> {
345373
return Change.fromObjects(before, after);

0 commit comments

Comments
 (0)