Skip to content

Commit 41e02f9

Browse files
authored
refactor: Move the logic to load user's function to loader.ts (#136)
* refactor: Move the logic to load user's function to loader.ts This separates the logic to load user's function and one to start the webserver. Moves the function signature type definition to function.ts. Also, adds more testcases for the function loading logic. See also: #134 * fix: Add comments to test functions * fix: Fix loader.ts tests * fix: Remove unnecessary closure
1 parent 5cccb4c commit 41e02f9

File tree

10 files changed

+337
-214
lines changed

10 files changed

+337
-214
lines changed

src/functions.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import * as express from 'express';
16+
17+
export interface HttpFunction {
18+
// tslint:disable-next-line:no-any express interface.
19+
(req: express.Request, res: express.Response): any;
20+
}
21+
export interface EventFunction {
22+
// tslint:disable-next-line:no-any
23+
(data: {}, context: Context): any;
24+
}
25+
export interface EventFunctionWithCallback {
26+
// tslint:disable-next-line:no-any
27+
(data: {}, context: Context, callback: Function): any;
28+
}
29+
export type HandlerFunction =
30+
| HttpFunction
31+
| EventFunction
32+
| EventFunctionWithCallback;
33+
34+
/**
35+
* The Cloud Functions context object for the event.
36+
*
37+
* @link https://cloud.google.com/functions/docs/writing/background#function_parameters
38+
*/
39+
export interface CloudFunctionsContext {
40+
/**
41+
* A unique ID for the event. For example: "70172329041928".
42+
*/
43+
eventId?: string;
44+
/**
45+
* The date/time this event was created. For example: "2018-04-09T07:56:12.975Z"
46+
* This will be formatted as ISO 8601.
47+
*/
48+
timestamp?: string;
49+
/**
50+
* The type of the event. For example: "google.pubsub.topic.publish".
51+
*/
52+
eventType?: string;
53+
/**
54+
* The resource that emitted the event.
55+
*/
56+
resource?: string;
57+
}
58+
59+
/**
60+
* The CloudEvents v0.2 context object for the event.
61+
*
62+
* @link https://github.com/cloudevents/spec/blob/v0.2/spec.md#context-attributes
63+
*/
64+
export interface CloudEventsContext {
65+
/**
66+
* Type of occurrence which has happened.
67+
*/
68+
type?: string;
69+
/**
70+
* The version of the CloudEvents specification which the event uses.
71+
*/
72+
specversion?: string;
73+
/**
74+
* The event producer.
75+
*/
76+
source?: string;
77+
/**
78+
* ID of the event.
79+
*/
80+
id?: string;
81+
/**
82+
* Timestamp of when the event happened.
83+
*/
84+
time?: string;
85+
/**
86+
* A link to the schema that the event data adheres to.
87+
*/
88+
schemaurl?: string;
89+
/**
90+
* Content type of the event data.
91+
*/
92+
contenttype?: string;
93+
94+
// tslint:disable-next-line:no-any CloudEvents extension attributes.
95+
[key: string]: any;
96+
}
97+
98+
export type Context = CloudFunctionsContext | CloudEventsContext;

src/index.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,9 @@
3030
import * as minimist from 'minimist';
3131
import { resolve } from 'path';
3232

33-
import {
34-
ErrorHandler,
35-
SignatureType,
36-
getServer,
37-
getUserFunction,
38-
} from './invoker';
33+
import { getUserFunction } from './loader';
34+
35+
import { ErrorHandler, SignatureType, getServer } from './invoker';
3936

4037
// Supported command-line flags
4138
const FLAG = {

src/invoker.ts

Lines changed: 8 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -29,89 +29,14 @@ import * as onFinished from 'on-finished';
2929
import { FUNCTION_STATUS_HEADER_FIELD } from './types';
3030
import { logAndSendError } from './logger';
3131
import { isBinaryCloudEvent } from './cloudevents';
32-
33-
/**
34-
* The Cloud Functions context object for the event.
35-
*
36-
* @link https://cloud.google.com/functions/docs/writing/background#function_parameters
37-
*/
38-
export interface CloudFunctionsContext {
39-
/**
40-
* A unique ID for the event. For example: "70172329041928".
41-
*/
42-
eventId?: string;
43-
/**
44-
* The date/time this event was created. For example: "2018-04-09T07:56:12.975Z"
45-
* This will be formatted as ISO 8601.
46-
*/
47-
timestamp?: string;
48-
/**
49-
* The type of the event. For example: "google.pubsub.topic.publish".
50-
*/
51-
eventType?: string;
52-
/**
53-
* The resource that emitted the event.
54-
*/
55-
resource?: string;
56-
}
57-
58-
/**
59-
* The CloudEvents v0.2 context object for the event.
60-
*
61-
* @link https://github.com/cloudevents/spec/blob/v0.2/spec.md#context-attributes
62-
*/
63-
export interface CloudEventsContext {
64-
/**
65-
* Type of occurrence which has happened.
66-
*/
67-
type?: string;
68-
/**
69-
* The version of the CloudEvents specification which the event uses.
70-
*/
71-
specversion?: string;
72-
/**
73-
* The event producer.
74-
*/
75-
source?: string;
76-
/**
77-
* ID of the event.
78-
*/
79-
id?: string;
80-
/**
81-
* Timestamp of when the event happened.
82-
*/
83-
time?: string;
84-
/**
85-
* A link to the schema that the event data adheres to.
86-
*/
87-
schemaurl?: string;
88-
/**
89-
* Content type of the event data.
90-
*/
91-
contenttype?: string;
92-
93-
// tslint:disable-next-line:no-any CloudEvents extension attributes.
94-
[key: string]: any;
95-
}
96-
97-
export type Context = CloudFunctionsContext | CloudEventsContext;
98-
99-
export interface HttpFunction {
100-
// tslint:disable-next-line:no-any express interface.
101-
(req: express.Request, res: express.Response): any;
102-
}
103-
export interface EventFunctionWithCallback {
104-
// tslint:disable-next-line:no-any
105-
(data: {}, context: Context, callback: Function): any;
106-
}
107-
export interface EventFunction {
108-
// tslint:disable-next-line:no-any
109-
(data: {}, context: Context): any;
110-
}
111-
export type HandlerFunction =
112-
| HttpFunction
113-
| EventFunction
114-
| EventFunctionWithCallback;
32+
import {
33+
HttpFunction,
34+
EventFunction,
35+
EventFunctionWithCallback,
36+
HandlerFunction,
37+
CloudFunctionsContext,
38+
CloudEventsContext,
39+
} from './functions';
11540

11641
// We optionally annotate the express Request with a rawBody field.
11742
// Express leaves the Express namespace open to allow merging of new fields.
@@ -141,95 +66,9 @@ function isHttpFunction(
14166
return functionSignatureType === SignatureType.HTTP;
14267
}
14368

144-
/**
145-
* Returns user's function from function file.
146-
* Returns null if function can't be retrieved.
147-
* @return User's function or null.
148-
*/
149-
export function getUserFunction(
150-
codeLocation: string,
151-
functionTarget: string
152-
): HandlerFunction | null {
153-
try {
154-
const functionModulePath = getFunctionModulePath(codeLocation);
155-
if (functionModulePath === null) {
156-
console.error('Provided code is not a loadable module.');
157-
return null;
158-
}
159-
160-
const functionModule = require(functionModulePath);
161-
let userFunction = functionTarget
162-
.split('.')
163-
.reduce((code, functionTargetPart) => {
164-
if (typeof code === 'undefined') {
165-
return undefined;
166-
} else {
167-
return code[functionTargetPart];
168-
}
169-
}, functionModule);
170-
171-
// TODO: do we want 'function' fallback?
172-
if (typeof userFunction === 'undefined') {
173-
if (functionModule.hasOwnProperty('function')) {
174-
userFunction = functionModule['function'];
175-
} else {
176-
console.error(
177-
`Function '${functionTarget}' is not defined in the provided ` +
178-
'module.\nDid you specify the correct target function to execute?'
179-
);
180-
return null;
181-
}
182-
}
183-
184-
if (typeof userFunction !== 'function') {
185-
console.error(
186-
`'${functionTarget}' needs to be of type function. Got: ` +
187-
`${typeof userFunction}`
188-
);
189-
return null;
190-
}
191-
192-
return userFunction as HandlerFunction;
193-
} catch (ex) {
194-
let additionalHint: string;
195-
// TODO: this should be done based on ex.code rather than string matching.
196-
if (ex.stack && ex.stack.includes('Cannot find module')) {
197-
additionalHint =
198-
'Did you list all required modules in the package.json ' +
199-
'dependencies?\n';
200-
} else {
201-
additionalHint = 'Is there a syntax error in your code?\n';
202-
}
203-
console.error(
204-
`Provided module can't be loaded.\n${additionalHint}` +
205-
`Detailed stack trace: ${ex.stack}`
206-
);
207-
return null;
208-
}
209-
}
210-
21169
// Response object for the most recent request.
21270
let latestRes: express.Response | null = null;
21371

214-
/**
215-
* Returns resolved path to the module containing the user function.
216-
* Returns null if the module can not be identified.
217-
* @param codeLocation Directory with user's code.
218-
* @return Resolved path or null.
219-
*/
220-
function getFunctionModulePath(codeLocation: string): string | null {
221-
let path: string | null = null;
222-
try {
223-
path = require.resolve(codeLocation);
224-
} catch (ex) {
225-
try {
226-
// TODO: Decide if we want to keep this fallback.
227-
path = require.resolve(codeLocation + '/function.js');
228-
} catch (ex) {}
229-
}
230-
return path;
231-
}
232-
23372
/**
23473
* Sends back a response to the incoming request.
23574
* @param result Output from function execution.

0 commit comments

Comments
 (0)