diff --git a/src/WorkerChannel.ts b/src/WorkerChannel.ts index 300bfba8..5366c68d 100644 --- a/src/WorkerChannel.ts +++ b/src/WorkerChannel.ts @@ -227,11 +227,18 @@ export class WorkerChannel implements IWorkerChannel { // explicitly set outputData to empty array to concat later response.outputData = []; + // As legacy behavior, falsy values get serialized to `null` in AzFunctions. + // This breaks Durable Functions expectations, where customers expect any + // JSON-serializable values to be preserved by the framework, + // so we check if we're serializing for durable and, if so, ensure falsy + // values get serialized. + let isDurableBinding = info?.bindings?.name?.type == 'activityTrigger'; + try { - if (result) { + if (result || (isDurableBinding && result != null)) { let returnBinding = info.getReturnBinding(); // Set results from return / context.done - if (result.return) { + if (result.return || (isDurableBinding && result.return != null)) { if (this._v1WorkerBehavior) { response.returnValue = toTypedData(result.return); } else { diff --git a/test/WorkerChannelTests.ts b/test/WorkerChannelTests.ts index 6be7d243..0a21840a 100644 --- a/test/WorkerChannelTests.ts +++ b/test/WorkerChannelTests.ts @@ -86,6 +86,11 @@ describe('WorkerChannel', () => { direction: 1, dataType: 1 }; + const activityTriggerBinding = { + type: "activityTrigger", + direction: 1, + dataType: 1 + }; const httpInputBinding = { type: "httpTrigger", direction: 0, @@ -126,6 +131,11 @@ describe('WorkerChannel', () => { test: orchestrationTriggerBinding } }; + const activityBinding = { + bindings: { + name: activityTriggerBinding + } + } const queueTriggerBinding = { bindings: { test: { @@ -690,5 +700,46 @@ describe('WorkerChannel', () => { }); }); + it ('returns and serializes falsy value in Durable: ""', () => { + loader.getFunc.returns((context) => context.done(null, "")); + loader.getInfo.returns(new FunctionInfo(activityBinding)); + + sendInvokeMessage([], getHttpTriggerDataMock()); + + const expectedOutput = []; + const expectedReturnValue = { + string: "" + }; + assertInvocationSuccess(expectedOutput, expectedReturnValue); + }); + + it ('returns and serializes falsy value in Durable: 0', () => { + loader.getFunc.returns((context) => context.done(null, 0)); + loader.getInfo.returns(new FunctionInfo(activityBinding)); + + sendInvokeMessage([], getHttpTriggerDataMock()); + + const expectedOutput = []; + const expectedReturnValue = { + int: 0 + }; + assertInvocationSuccess(expectedOutput, expectedReturnValue); + }); + + it ('returns and serializes falsy value in Durable: false', () => { + loader.getFunc.returns((context) => context.done(null, false)); + loader.getInfo.returns(new FunctionInfo(activityBinding)); + + sendInvokeMessage([], getHttpTriggerDataMock()); + + const expectedOutput = []; + const expectedReturnValue = { + json: "false" + }; + assertInvocationSuccess(expectedOutput, expectedReturnValue) + }); + }); + + }) \ No newline at end of file