From 9cf46edc9b1b890e2a6fd1aec8d818074585783b Mon Sep 17 00:00:00 2001 From: Matthew Robertson Date: Wed, 22 Sep 2021 17:20:45 -0700 Subject: [PATCH] refactor!: use a union type for SignatureType (#331) This commit refactors the SignatureType enum into an array of strings declared [as const](https://github.com/Microsoft/TypeScript/pull/29510). This allows the SignatureType type to be expressed as a union type, which works a bit better when parsing a users provided string. This is some simple refactoring in preparation for declarative function signatures. BREAKING CHANGE: exported SignatureType type is converted from an enum to a union type --- src/index.ts | 25 +++++++++---------------- src/router.ts | 4 ++-- src/server.ts | 8 ++++---- src/types.ts | 24 +++++++++++++++++++----- test/integration/cloudevent.ts | 3 +-- test/integration/http.ts | 3 +-- test/integration/legacy_event.ts | 3 +-- 7 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7a70aa49..306e00a4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,7 +36,7 @@ import {resolve} from 'path'; import {getUserFunction} from './loader'; import {ErrorHandler} from './invoker'; import {getServer} from './server'; -import {SignatureType} from './types'; +import {SignatureType, isValidSignatureType} from './types'; // Supported command-line flags const FLAG = { @@ -54,10 +54,6 @@ const ENV = { SOURCE: 'FUNCTION_SOURCE', }; -enum NodeEnv { - PRODUCTION = 'production', -} - const argv = minimist(process.argv, { string: [FLAG.PORT, FLAG.TARGET, FLAG.SIGNATURE_TYPE], }); @@ -68,17 +64,14 @@ const CODE_LOCATION = resolve( const PORT = argv[FLAG.PORT] || process.env[ENV.PORT] || '8080'; const TARGET = argv[FLAG.TARGET] || process.env[ENV.TARGET] || 'function'; -const SIGNATURE_TYPE_STRING = - argv[FLAG.SIGNATURE_TYPE] || process.env[ENV.SIGNATURE_TYPE] || 'http'; -const SIGNATURE_TYPE = - SignatureType[ - SIGNATURE_TYPE_STRING.toUpperCase() as keyof typeof SignatureType - ]; -if (SIGNATURE_TYPE === undefined) { +const SIGNATURE_TYPE = ( + argv[FLAG.SIGNATURE_TYPE] || + process.env[ENV.SIGNATURE_TYPE] || + 'http' +).toLowerCase(); +if (!isValidSignatureType(SIGNATURE_TYPE)) { console.error( - `Function signature type must be one of: ${Object.values( - SignatureType - ).join(', ')}.` + `Function signature type must be one of: ${SignatureType.join(', ')}.` ); // eslint-disable-next-line no-process-exit process.exit(1); @@ -108,7 +101,7 @@ getUserFunction(CODE_LOCATION, TARGET).then(userFunction => { SERVER.listen(PORT, () => { ERROR_HANDLER.register(); - if (process.env.NODE_ENV !== NodeEnv.PRODUCTION) { + if (process.env.NODE_ENV !== 'production') { console.log('Serving function...'); console.log(`Function: ${TARGET}`); console.log(`Signature type: ${SIGNATURE_TYPE}`); diff --git a/src/router.ts b/src/router.ts index cc2e92d3..d81a6c33 100644 --- a/src/router.ts +++ b/src/router.ts @@ -39,7 +39,7 @@ export function registerFunctionRoutes( userFunction: HandlerFunction, functionSignatureType: SignatureType ) { - if (functionSignatureType === SignatureType.HTTP) { + if (functionSignatureType === 'http') { app.use('/favicon.ico|/robots.txt', (req, res) => { // Neither crawlers nor browsers attempting to pull the icon find the body // contents particularly useful, so we send nothing in the response body. @@ -57,7 +57,7 @@ export function registerFunctionRoutes( const handler = makeHttpHandler(userFunction as HttpFunction); handler(req, res, next); }); - } else if (functionSignatureType === SignatureType.EVENT) { + } else if (functionSignatureType === 'event') { app.post('/*', (req, res, next) => { const wrappedUserFunction = wrapEventFunction( userFunction as EventFunction | EventFunctionWithCallback diff --git a/src/server.ts b/src/server.ts index 1f743459..43de40e6 100644 --- a/src/server.ts +++ b/src/server.ts @@ -101,8 +101,8 @@ export function getServer( app.disable('x-powered-by'); if ( - functionSignatureType === SignatureType.EVENT || - functionSignatureType === SignatureType.CLOUDEVENT + functionSignatureType === 'event' || + functionSignatureType === 'cloudevent' ) { // If a Pub/Sub subscription is configured to invoke a user's function directly, the request body // needs to be marshalled into the structure that wrapEventFunction expects. This unblocks local @@ -110,10 +110,10 @@ export function getServer( app.use(legacyPubSubEventMiddleware); } - if (functionSignatureType === SignatureType.EVENT) { + if (functionSignatureType === 'event') { app.use(cloudeventToBackgroundEventMiddleware); } - if (functionSignatureType === SignatureType.CLOUDEVENT) { + if (functionSignatureType === 'cloudevent') { app.use(backgroundEventToCloudEventMiddleware); } diff --git a/src/types.ts b/src/types.ts index ec0d77dd..7d2d2f5b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,8 +16,22 @@ // executing the client function. export const FUNCTION_STATUS_HEADER_FIELD = 'X-Google-Status'; -export enum SignatureType { - HTTP = 'http', - EVENT = 'event', - CLOUDEVENT = 'cloudevent', -} +/** + * List of function signature types that are supported by the framework. + */ +export const SignatureType = ['http', 'event', 'cloudevent'] as const; + +/** + * Union type of all valid function SignatureType values. + */ +export type SignatureType = typeof SignatureType[number]; + +/** + * Type guard to test if a provided value is valid SignatureType + * @param x the value to test + * @returns true if the provided value is a valid SignatureType + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isValidSignatureType = (x: any): x is SignatureType => { + return SignatureType.includes(x); +}; diff --git a/test/integration/cloudevent.ts b/test/integration/cloudevent.ts index 7355792f..5ebb7157 100644 --- a/test/integration/cloudevent.ts +++ b/test/integration/cloudevent.ts @@ -16,7 +16,6 @@ import * as assert from 'assert'; import * as functions from '../../src/functions'; import * as sinon from 'sinon'; import {getServer} from '../../src/server'; -import {SignatureType} from '../../src/types'; import * as supertest from 'supertest'; const TEST_CLOUD_EVENT = { @@ -224,7 +223,7 @@ describe('CloudEvent Function', () => { let receivedCloudEvent: functions.CloudEventsContext | null = null; const server = getServer((cloudevent: functions.CloudEventsContext) => { receivedCloudEvent = cloudevent as functions.CloudEventsContext; - }, SignatureType.CLOUDEVENT); + }, 'cloudevent'); await supertest(server) .post('/') .set(test.headers) diff --git a/test/integration/http.ts b/test/integration/http.ts index 3caa6665..68418f8f 100644 --- a/test/integration/http.ts +++ b/test/integration/http.ts @@ -15,7 +15,6 @@ import * as assert from 'assert'; import * as express from 'express'; import {getServer} from '../../src/server'; -import {SignatureType} from '../../src/types'; import * as supertest from 'supertest'; describe('HTTP Function', () => { @@ -73,7 +72,7 @@ describe('HTTP Function', () => { query: req.query.param, }); }, - SignatureType.HTTP + 'http' ); const st = supertest(server); await (test.httpVerb === 'GET' diff --git a/test/integration/legacy_event.ts b/test/integration/legacy_event.ts index e4d78ad2..410fd322 100644 --- a/test/integration/legacy_event.ts +++ b/test/integration/legacy_event.ts @@ -15,7 +15,6 @@ import * as assert from 'assert'; import * as functions from '../../src/functions'; import {getServer} from '../../src/server'; -import {SignatureType} from '../../src/types'; import * as supertest from 'supertest'; const TEST_CLOUD_EVENT = { @@ -175,7 +174,7 @@ describe('Event Function', () => { const server = getServer((data: {}, context: functions.Context) => { receivedData = data; receivedContext = context as functions.CloudFunctionsContext; - }, SignatureType.EVENT); + }, 'event'); const requestHeaders = { 'Content-Type': 'application/json', ...test.headers,