Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: use a union type for SignatureType #331

Merged
merged 1 commit into from Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 9 additions & 16 deletions src/index.ts
Expand Up @@ -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 = {
Expand All @@ -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],
});
Expand All @@ -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);
Expand Down Expand Up @@ -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') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not seeing TS error for misconfigured strings with all.

For example, if I type:

if (process.env.NODE_ENV !== 'prodduction') {

here, I won't get a TS error. I'd rather just be able to type NodeEnv. and see an autocompleted result of options and errors if I mistype.

console.log('Serving function...');
console.log(`Function: ${TARGET}`);
console.log(`Signature type: ${SIGNATURE_TYPE}`);
Expand Down
4 changes: 2 additions & 2 deletions src/router.ts
Expand Up @@ -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.
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/server.ts
Expand Up @@ -101,19 +101,19 @@ 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
// development with the Pub/Sub emulator
app.use(legacyPubSubEventMiddleware);
}

if (functionSignatureType === SignatureType.EVENT) {
if (functionSignatureType === 'event') {
app.use(cloudeventToBackgroundEventMiddleware);
}
if (functionSignatureType === SignatureType.CLOUDEVENT) {
if (functionSignatureType === 'cloudevent') {
app.use(backgroundEventToCloudEventMiddleware);
}

Expand Down
24 changes: 19 additions & 5 deletions src/types.ts
Expand Up @@ -16,8 +16,22 @@
// executing the client function.
export const FUNCTION_STATUS_HEADER_FIELD = 'X-Google-Status';

export enum SignatureType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree with using string literals and arrays instead of enums. Enums provide benefits over literal values such as better typing, autocompletion, and less code here. We don't plan an arbitrary signature type until the FF contract changes.

This is also a breaking change to the API, not an internal refactor.

Could we discuss a bit more about the tradeoff in the PR / an issue?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're fine changing to the union types, but we shouldn't completely remove the enum as it's an exported interface, unless we have strong reason to introduce a breaking change.

For example, we had a customer complaint about a similar small interface change:
#216

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we give customer an opportunity to pass the value to use in any meaningful way (the signature type can only be provided as an env var or cli flag), so I don't think we should be too concerned about changing this. I see your point about why this is breaking change now. I will updated the conventional commit tag.

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);
};
3 changes: 1 addition & 2 deletions test/integration/cloudevent.ts
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions test/integration/http.ts
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -73,7 +72,7 @@ describe('HTTP Function', () => {
query: req.query.param,
});
},
SignatureType.HTTP
'http'
);
const st = supertest(server);
await (test.httpVerb === 'GET'
Expand Down
3 changes: 1 addition & 2 deletions test/integration/legacy_event.ts
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
Expand Down