From af7a31482b84106d3fabe6a47026c54ddb7a5cb6 Mon Sep 17 00:00:00 2001 From: David Justo Date: Fri, 22 Jan 2021 11:25:38 -0800 Subject: [PATCH 01/12] Introduce Truthy type, propagate changes --- src/Context.ts | 20 ++++++++++++++------ src/WorkerChannel.ts | 16 +++++++++------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/Context.ts b/src/Context.ts index 301d3694..3e2c5f98 100644 --- a/src/Context.ts +++ b/src/Context.ts @@ -8,7 +8,7 @@ import LogCategory = rpc.RpcLog.RpcLogCategory; import { Context, ExecutionContext, Logger, BindingDefinition, HttpRequest, TraceContext } from './public/Interfaces' import { v4 as uuid } from 'uuid' -export function CreateContextAndInputs(info: FunctionInfo, request: rpc.IInvocationRequest, logCallback: LogCallback, callback: ResultCallback, v1WorkerBehavior: boolean) { +export function CreateContextAndInputs(info: FunctionInfo, request: rpc.IInvocationRequest, logCallback: LogCallback, callback: ResultCallback, v1WorkerBehavior: boolean) { const context = new InvocationContext(info, request, logCallback, callback); const bindings: Dict = {}; @@ -57,7 +57,7 @@ export function CreateContextAndInputs(info: FunctionInfo, request: rpc.IInvocat } } -class InvocationContext implements Context { +class InvocationContext implements Context { invocationId: string; executionContext: ExecutionContext; bindings: Dict; @@ -69,7 +69,7 @@ class InvocationContext implements Context { res?: Response; done: DoneCallback; - constructor(info: FunctionInfo, request: rpc.IInvocationRequest, logCallback: LogCallback, callback: ResultCallback) { + constructor(info: FunctionInfo, request: rpc.IInvocationRequest, logCallback: LogCallback, callback: ResultCallback) { this.invocationId = request.invocationId; this.traceContext = fromRpcTraceContext(request.traceContext); const executionContext = { @@ -133,8 +133,8 @@ function logWithAsyncCheck(done: boolean, log: LogCallback, level: LogLevel, exe return log(level, LogCategory.User, ...args); } -export interface InvocationResult { - return: any; +export interface InvocationResult { + return: Truthy | undefined; bindings: Dict; } @@ -142,8 +142,16 @@ export type DoneCallback = (err?: Error | string, result?: any) => void; export type LogCallback = (level: LogLevel, category: rpc.RpcLog.RpcLogCategory, ...args: any[]) => void; -export type ResultCallback = (err?: any, result?: InvocationResult) => void; +export type ResultCallback = (err?: any, result?: InvocationResult) => void; export interface Dict { [key: string]: T } + +export type Truthy = + false extends T ? never : + 0 extends T ? never : + "" extends T ? never : + null extends T ? never : + undefined extends T ? never : + T \ No newline at end of file diff --git a/src/WorkerChannel.ts b/src/WorkerChannel.ts index 172e4a78..a94f941c 100644 --- a/src/WorkerChannel.ts +++ b/src/WorkerChannel.ts @@ -201,7 +201,7 @@ export class WorkerChannel implements IWorkerChannel { * @param requestId gRPC message request id * @param msg gRPC message content */ - public invocationRequest(requestId: string, msg: rpc.InvocationRequest) { + public invocationRequest(requestId: string, msg: rpc.InvocationRequest) { // Repopulate triggerMetaData if http. if (this._v1WorkerBehavior) { augmentTriggerMetadata(msg); @@ -218,7 +218,7 @@ export class WorkerChannel implements IWorkerChannel { }); } - let resultCallback: ResultCallback = (err, result) => { + let resultCallback: ResultCallback = (err, result) => { let response: rpc.IInvocationResponse = { invocationId: msg.invocationId, result: this.getStatus(err) @@ -227,10 +227,10 @@ export class WorkerChannel implements IWorkerChannel { response.outputData = []; try { - if (result) { + if (result !== undefined) { let returnBinding = info.getReturnBinding(); // Set results from return / context.done - if (result.return) { + if (result.return !== undefined) { if (this._v1WorkerBehavior) { response.returnValue = toTypedData(result.return); } else { @@ -239,11 +239,12 @@ export class WorkerChannel implements IWorkerChannel { response.returnValue = returnBinding.converter(result.return); // $return binding is not found: read result as object of outputs } else { + const returnVal = result.return; // necessary to type-check response.outputData = Object.keys(info.outputBindings) - .filter(key => result.return[key] !== undefined) + .filter(key => returnVal[key] !== undefined) .map(key => { name: key, - data: info.outputBindings[key].converter(result.return[key]) + data: info.outputBindings[key].converter(returnVal[key]) }); } // returned value does not match any output bindings (named or $return) @@ -261,7 +262,8 @@ export class WorkerChannel implements IWorkerChannel { let definedInBindings: boolean = result.bindings[key] !== undefined; let hasReturnValue: boolean = !!result.return; let hasReturnBinding: boolean = !!returnBinding; - let definedInReturn: boolean = hasReturnValue && !hasReturnBinding && result.return[key] !== undefined; + let hasReturnAtKey: boolean = (result.return) ? (result.return[key] != undefined) : false; + let definedInReturn: boolean = hasReturnValue && !hasReturnBinding && hasReturnAtKey; return definedInBindings && !definedInReturn; }) .map(key => { From 265d235c9b28f01b5bbba86795f57e9e1b091e24 Mon Sep 17 00:00:00 2001 From: David Justo Date: Mon, 25 Jan 2021 14:19:37 -0800 Subject: [PATCH 02/12] Undo Truty-type changes, keep simple fix --- src/Context.ts | 24 ++++++++---------------- src/WorkerChannel.ts | 9 ++++----- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/Context.ts b/src/Context.ts index 3e2c5f98..4439d95d 100644 --- a/src/Context.ts +++ b/src/Context.ts @@ -8,7 +8,7 @@ import LogCategory = rpc.RpcLog.RpcLogCategory; import { Context, ExecutionContext, Logger, BindingDefinition, HttpRequest, TraceContext } from './public/Interfaces' import { v4 as uuid } from 'uuid' -export function CreateContextAndInputs(info: FunctionInfo, request: rpc.IInvocationRequest, logCallback: LogCallback, callback: ResultCallback, v1WorkerBehavior: boolean) { +export function CreateContextAndInputs(info: FunctionInfo, request: rpc.IInvocationRequest, logCallback: LogCallback, callback: ResultCallback, v1WorkerBehavior: boolean) { const context = new InvocationContext(info, request, logCallback, callback); const bindings: Dict = {}; @@ -37,7 +37,7 @@ export function CreateContextAndInputs(info: FunctionInfo, request: rpc.IInvo if (httpInput) { context.req = new Request(httpInput); context.res = new Response(context.done); - // This is added for backwards compatability with what the host used to send to the worker + // This is added for backwards compatibility with what the host used to send to the worker context.bindingData.sys = { methodName: info.name, utcNow: (new Date()).toISOString(), @@ -57,7 +57,7 @@ export function CreateContextAndInputs(info: FunctionInfo, request: rpc.IInvo } } -class InvocationContext implements Context { +class InvocationContext implements Context { invocationId: string; executionContext: ExecutionContext; bindings: Dict; @@ -69,7 +69,7 @@ class InvocationContext implements Context { res?: Response; done: DoneCallback; - constructor(info: FunctionInfo, request: rpc.IInvocationRequest, logCallback: LogCallback, callback: ResultCallback) { + constructor(info: FunctionInfo, request: rpc.IInvocationRequest, logCallback: LogCallback, callback: ResultCallback) { this.invocationId = request.invocationId; this.traceContext = fromRpcTraceContext(request.traceContext); const executionContext = { @@ -133,8 +133,8 @@ function logWithAsyncCheck(done: boolean, log: LogCallback, level: LogLevel, exe return log(level, LogCategory.User, ...args); } -export interface InvocationResult { - return: Truthy | undefined; +export interface InvocationResult { + return: any; bindings: Dict; } @@ -142,16 +142,8 @@ export type DoneCallback = (err?: Error | string, result?: any) => void; export type LogCallback = (level: LogLevel, category: rpc.RpcLog.RpcLogCategory, ...args: any[]) => void; -export type ResultCallback = (err?: any, result?: InvocationResult) => void; +export type ResultCallback = (err?: any, result?: InvocationResult) => void; export interface Dict { [key: string]: T -} - -export type Truthy = - false extends T ? never : - 0 extends T ? never : - "" extends T ? never : - null extends T ? never : - undefined extends T ? never : - T \ No newline at end of file +} \ No newline at end of file diff --git a/src/WorkerChannel.ts b/src/WorkerChannel.ts index a94f941c..b18c5d6a 100644 --- a/src/WorkerChannel.ts +++ b/src/WorkerChannel.ts @@ -201,7 +201,7 @@ export class WorkerChannel implements IWorkerChannel { * @param requestId gRPC message request id * @param msg gRPC message content */ - public invocationRequest(requestId: string, msg: rpc.InvocationRequest) { + public invocationRequest(requestId: string, msg: rpc.InvocationRequest) { // Repopulate triggerMetaData if http. if (this._v1WorkerBehavior) { augmentTriggerMetadata(msg); @@ -218,7 +218,7 @@ export class WorkerChannel implements IWorkerChannel { }); } - let resultCallback: ResultCallback = (err, result) => { + let resultCallback: ResultCallback = (err, result) => { let response: rpc.IInvocationResponse = { invocationId: msg.invocationId, result: this.getStatus(err) @@ -239,12 +239,11 @@ export class WorkerChannel implements IWorkerChannel { response.returnValue = returnBinding.converter(result.return); // $return binding is not found: read result as object of outputs } else { - const returnVal = result.return; // necessary to type-check response.outputData = Object.keys(info.outputBindings) - .filter(key => returnVal[key] !== undefined) + .filter(key => result.return[key] !== undefined) .map(key => { name: key, - data: info.outputBindings[key].converter(returnVal[key]) + data: info.outputBindings[key].converter(result.return[key]) }); } // returned value does not match any output bindings (named or $return) From 7aaae7d7dd245eb07b597a7d875f18cca2cbbf1b Mon Sep 17 00:00:00 2001 From: David Justo Date: Mon, 25 Jan 2021 14:28:25 -0800 Subject: [PATCH 03/12] add back old interface types --- types/public/Interfaces.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/types/public/Interfaces.d.ts b/types/public/Interfaces.d.ts index 0ba4dca3..5a436b9f 100644 --- a/types/public/Interfaces.d.ts +++ b/types/public/Interfaces.d.ts @@ -74,13 +74,13 @@ export interface Context { }; } export interface HttpRequestHeaders { - [name: string]: string; + [name: string]: string | undefined; } export interface HttpRequestQuery { - [name: string]: string; + [name: string]: string | undefined; } export interface HttpRequestParams { - [name: string]: string; + [name: string]: string | undefined; } /** * HTTP request object. Provided to your function when using HTTP Bindings. From 052c7a94470642a46e3067d0eeb7e94ab29e613c Mon Sep 17 00:00:00 2001 From: David Justo Date: Mon, 25 Jan 2021 14:48:00 -0800 Subject: [PATCH 04/12] remove unecessary bool checks --- src/WorkerChannel.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/WorkerChannel.ts b/src/WorkerChannel.ts index b18c5d6a..74067542 100644 --- a/src/WorkerChannel.ts +++ b/src/WorkerChannel.ts @@ -261,8 +261,7 @@ export class WorkerChannel implements IWorkerChannel { let definedInBindings: boolean = result.bindings[key] !== undefined; let hasReturnValue: boolean = !!result.return; let hasReturnBinding: boolean = !!returnBinding; - let hasReturnAtKey: boolean = (result.return) ? (result.return[key] != undefined) : false; - let definedInReturn: boolean = hasReturnValue && !hasReturnBinding && hasReturnAtKey; + let definedInReturn: boolean = hasReturnValue && !hasReturnBinding && result.return[key] !== undefined;y; return definedInBindings && !definedInReturn; }) .map(key => { From 96734c46c565e8c49d9fe780e17990e3b3e5e6ae Mon Sep 17 00:00:00 2001 From: David Justo Date: Mon, 25 Jan 2021 14:48:45 -0800 Subject: [PATCH 05/12] remove accidental typo --- src/WorkerChannel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WorkerChannel.ts b/src/WorkerChannel.ts index 74067542..807fd426 100644 --- a/src/WorkerChannel.ts +++ b/src/WorkerChannel.ts @@ -261,7 +261,7 @@ export class WorkerChannel implements IWorkerChannel { let definedInBindings: boolean = result.bindings[key] !== undefined; let hasReturnValue: boolean = !!result.return; let hasReturnBinding: boolean = !!returnBinding; - let definedInReturn: boolean = hasReturnValue && !hasReturnBinding && result.return[key] !== undefined;y; + let definedInReturn: boolean = hasReturnValue && !hasReturnBinding && result.return[key] !== undefined; return definedInBindings && !definedInReturn; }) .map(key => { From 811bcf64a46bf68cee8141fbde1e83810d78fb1b Mon Sep 17 00:00:00 2001 From: David Justo Date: Mon, 25 Jan 2021 16:02:18 -0800 Subject: [PATCH 06/12] Make type checks against null instead of undefined --- src/WorkerChannel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WorkerChannel.ts b/src/WorkerChannel.ts index 807fd426..cc3315d8 100644 --- a/src/WorkerChannel.ts +++ b/src/WorkerChannel.ts @@ -227,10 +227,10 @@ export class WorkerChannel implements IWorkerChannel { response.outputData = []; try { - if (result !== undefined) { + if (result != null) { let returnBinding = info.getReturnBinding(); // Set results from return / context.done - if (result.return !== undefined) { + if (result.return != null) { if (this._v1WorkerBehavior) { response.returnValue = toTypedData(result.return); } else { From b593c808fd4afbb98db493118b8b78adc5cd48bf Mon Sep 17 00:00:00 2001 From: David Justo Date: Mon, 25 Jan 2021 16:11:48 -0800 Subject: [PATCH 07/12] add newline --- src/Worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Worker.ts b/src/Worker.ts index 84cbe527..55cb4f6b 100644 --- a/src/Worker.ts +++ b/src/Worker.ts @@ -57,4 +57,4 @@ export function startNodeWorker(args) { process.on('exit', code => { systemLog(`Worker ${workerId} exited with code ${code}`); }); -} \ No newline at end of file +} From aec12878569229b6b704c01dc345c8cbeee5fb14 Mon Sep 17 00:00:00 2001 From: David Justo Date: Mon, 25 Jan 2021 16:14:02 -0800 Subject: [PATCH 08/12] add newline to the right files --- src/Context.ts | 2 +- src/Worker.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Context.ts b/src/Context.ts index 4439d95d..2e6e2264 100644 --- a/src/Context.ts +++ b/src/Context.ts @@ -146,4 +146,4 @@ export type ResultCallback = (err?: any, result?: InvocationResult) => void; export interface Dict { [key: string]: T -} \ No newline at end of file +} diff --git a/src/Worker.ts b/src/Worker.ts index 55cb4f6b..84cbe527 100644 --- a/src/Worker.ts +++ b/src/Worker.ts @@ -57,4 +57,4 @@ export function startNodeWorker(args) { process.on('exit', code => { systemLog(`Worker ${workerId} exited with code ${code}`); }); -} +} \ No newline at end of file From 8bc0b02a748eff002f40c49d580b416983413570 Mon Sep 17 00:00:00 2001 From: David Justo Date: Mon, 25 Jan 2021 16:17:42 -0800 Subject: [PATCH 09/12] Reset interfaces changes --- types/public/Interfaces.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/types/public/Interfaces.d.ts b/types/public/Interfaces.d.ts index 5a436b9f..0ba4dca3 100644 --- a/types/public/Interfaces.d.ts +++ b/types/public/Interfaces.d.ts @@ -74,13 +74,13 @@ export interface Context { }; } export interface HttpRequestHeaders { - [name: string]: string | undefined; + [name: string]: string; } export interface HttpRequestQuery { - [name: string]: string | undefined; + [name: string]: string; } export interface HttpRequestParams { - [name: string]: string | undefined; + [name: string]: string; } /** * HTTP request object. Provided to your function when using HTTP Bindings. From 525ce4ac1c352b54ce515e4321b4079abe88710d Mon Sep 17 00:00:00 2001 From: David Justo Date: Wed, 27 Jan 2021 09:34:04 -0800 Subject: [PATCH 10/12] Add unit test serializing falsy values --- test/WorkerChannelTests.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/WorkerChannelTests.ts b/test/WorkerChannelTests.ts index 0cb903ab..0b4745d0 100644 --- a/test/WorkerChannelTests.ts +++ b/test/WorkerChannelTests.ts @@ -455,6 +455,39 @@ describe('WorkerChannel', () => { assertInvocationSuccess(expectedOutput, expectedReturnValue); }); + it ('returns and serializes falsy values', () => { + loader.getFunc.returns((context) => context.done(null, "")); + loader.getInfo.returns(new FunctionInfo(orchestratorBinding)); + + sendInvokeMessage([], getHttpTriggerDataMock()); + + const expectedOutput = []; + const expectedReturnValue1 = { + string: "" + }; + assertInvocationSuccess(expectedOutput, expectedReturnValue1); + + loader.getFunc.returns((context) => context.done(null, 0)); + loader.getInfo.returns(new FunctionInfo(orchestratorBinding)); + + sendInvokeMessage([], getHttpTriggerDataMock()); + + const expectedReturnValue2 = { + int: 0 + }; + assertInvocationSuccess(expectedOutput, expectedReturnValue2); + + loader.getFunc.returns((context) => context.done(null, false)); + loader.getInfo.returns(new FunctionInfo(orchestratorBinding)); + + sendInvokeMessage([], getHttpTriggerDataMock()); + + const expectedReturnValue3 = { + json: "false" + }; + assertInvocationSuccess(expectedOutput, expectedReturnValue3) + }); + it ('returned output is ignored if http', () => { loader.getFunc.returns((context) => context.done(null, ["hello, seattle!", "hello, tokyo!"])); loader.getInfo.returns(new FunctionInfo(httpResBinding)); From 7551e95407227adb072d4e3daa0826c552c53c41 Mon Sep 17 00:00:00 2001 From: David Justo Date: Wed, 27 Jan 2021 13:07:21 -0800 Subject: [PATCH 11/12] Split tests, fix indentation --- test/WorkerChannelTests.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/test/WorkerChannelTests.ts b/test/WorkerChannelTests.ts index 0b4745d0..1d236eed 100644 --- a/test/WorkerChannelTests.ts +++ b/test/WorkerChannelTests.ts @@ -455,37 +455,43 @@ describe('WorkerChannel', () => { assertInvocationSuccess(expectedOutput, expectedReturnValue); }); - it ('returns and serializes falsy values', () => { + it ('returns and serializes falsy value: ""', () => { loader.getFunc.returns((context) => context.done(null, "")); loader.getInfo.returns(new FunctionInfo(orchestratorBinding)); sendInvokeMessage([], getHttpTriggerDataMock()); const expectedOutput = []; - const expectedReturnValue1 = { + const expectedReturnValue = { string: "" }; - assertInvocationSuccess(expectedOutput, expectedReturnValue1); + assertInvocationSuccess(expectedOutput, expectedReturnValue); + }); + it ('returns and serializes falsy value: 0', () => { loader.getFunc.returns((context) => context.done(null, 0)); loader.getInfo.returns(new FunctionInfo(orchestratorBinding)); sendInvokeMessage([], getHttpTriggerDataMock()); - const expectedReturnValue2 = { + const expectedOutput = []; + const expectedReturnValue = { int: 0 }; - assertInvocationSuccess(expectedOutput, expectedReturnValue2); + assertInvocationSuccess(expectedOutput, expectedReturnValue); + }); + it ('returns and serializes falsy value: false', () => { loader.getFunc.returns((context) => context.done(null, false)); loader.getInfo.returns(new FunctionInfo(orchestratorBinding)); sendInvokeMessage([], getHttpTriggerDataMock()); - const expectedReturnValue3 = { + const expectedOutput = []; + const expectedReturnValue = { json: "false" }; - assertInvocationSuccess(expectedOutput, expectedReturnValue3) + assertInvocationSuccess(expectedOutput, expectedReturnValue) }); it ('returned output is ignored if http', () => { From 73416e21ce6e5881444317451e80304322951dcf Mon Sep 17 00:00:00 2001 From: David Justo Date: Wed, 27 Jan 2021 13:08:29 -0800 Subject: [PATCH 12/12] fix indentation --- test/WorkerChannelTests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/WorkerChannelTests.ts b/test/WorkerChannelTests.ts index 1d236eed..3204db7c 100644 --- a/test/WorkerChannelTests.ts +++ b/test/WorkerChannelTests.ts @@ -468,7 +468,7 @@ describe('WorkerChannel', () => { assertInvocationSuccess(expectedOutput, expectedReturnValue); }); - it ('returns and serializes falsy value: 0', () => { + it ('returns and serializes falsy value: 0', () => { loader.getFunc.returns((context) => context.done(null, 0)); loader.getInfo.returns(new FunctionInfo(orchestratorBinding));