Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/WorkerChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Copy link
Member

@alrod alrod May 26, 2021

Choose a reason for hiding this comment

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

if isDurableBinding == true the result can be (0, false, undef, '') ? Can you please add a comment describing why it happens for durable but not for regular executions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's correct, in durable bindings such as activityTriggers, the result can be 0, false, and "". This is because Durable Function activities are meant to be abstractions similar to regular function invocations, so the programming model should, in theory, allow for any output value that would be valid to return in regular JavaScript and is JSON serializable. This includes the values: 0, false, and "". This was also a behavior we used to have in the past, but it stopped working recently, as described here: Azure/azure-functions-durable-js#50

I've also expanded my comments in the PR to make this clearer :) Please let me know if there's anything else I can clarify!

As for serializing undefined, I think that gets serialized as null anyways. I can certainly write code to explicitly avoid serializing undefined though. Please let me know if you would prefer I added that.

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 {
Expand Down
51 changes: 51 additions & 0 deletions test/WorkerChannelTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ describe('WorkerChannel', () => {
direction: 1,
dataType: 1
};
const activityTriggerBinding = {
type: "activityTrigger",
direction: 1,
dataType: 1
};
const httpInputBinding = {
type: "httpTrigger",
direction: 0,
Expand Down Expand Up @@ -126,6 +131,11 @@ describe('WorkerChannel', () => {
test: orchestrationTriggerBinding
}
};
const activityBinding = {
bindings: {
name: activityTriggerBinding
}
}
const queueTriggerBinding = {
bindings: {
test: {
Expand Down Expand Up @@ -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)
});

});


})