diff --git a/release_notes.md b/release_notes.md index 4149fc4528..6984482934 100644 --- a/release_notes.md +++ b/release_notes.md @@ -3,6 +3,7 @@ +- Allow for an output binding value of an invocation result to be null (#10698) - Updated dotnet-isolated worker to 1.0.12. - [Corrected the path for the prelaunch app location.](https://github.com/Azure/azure-functions-dotnet-worker/pull/2897) - - [Added net9 prelaunch app.](https://github.com/Azure/azure-functions-dotnet-worker/pull/2898) \ No newline at end of file + - [Added net9 prelaunch app.](https://github.com/Azure/azure-functions-dotnet-worker/pull/2898) diff --git a/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs b/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs index 8317509661..4b19c2bc8b 100644 --- a/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs +++ b/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs @@ -1068,8 +1068,10 @@ private async Task GetBindingDataAsync(ParameterBinding binding, string case ParameterBindingType.Data: // Data was transferred by the worker using RPC return binding.Data.ToObject(); + case ParameterBindingType.None: + return null; default: - throw new InvalidOperationException("Unknown ParameterBindingType"); + throw new InvalidOperationException($"Unknown ParameterBindingType of type {binding.RpcDataCase}"); } } diff --git a/test/WebJobs.Script.Tests/Workers/Rpc/GrpcWorkerChannelTests.cs b/test/WebJobs.Script.Tests/Workers/Rpc/GrpcWorkerChannelTests.cs index 99d9855225..3cd81f8bc2 100644 --- a/test/WebJobs.Script.Tests/Workers/Rpc/GrpcWorkerChannelTests.cs +++ b/test/WebJobs.Script.Tests/Workers/Rpc/GrpcWorkerChannelTests.cs @@ -1546,6 +1546,20 @@ await TestHelpers.Await(() => GetInvocationLogs().Length == logLoop, } } + [Fact] + public async Task NullOutputBinding_DoesNotThrow() + { + await CreateDefaultWorkerChannel(); + + var invocationId = Guid.NewGuid(); + var resultSource = new TaskCompletionSource(); + ScriptInvocationContext scriptInvocationContext = GetTestScriptInvocationContext(invocationId, resultSource, logger: _logger); + await _workerChannel.SendInvocationRequest(scriptInvocationContext); + await _workerChannel.InvokeResponse(BuildSuccessfulInvocationResponseWithNullOutputBinding(invocationId.ToString())); + + Assert.Equal(TaskStatus.RanToCompletion, resultSource.Task.Status); + } + private static IEnumerable GetTestFunctionsList(string runtime, bool addWorkerProperties = false) { return GetTestFunctionsList(runtime, numberOfFunctions: 2, addWorkerProperties); @@ -1690,6 +1704,30 @@ private static InvocationResponse BuildSuccessfulInvocationResponse(string invoc }; } + private InvocationResponse BuildSuccessfulInvocationResponseWithNullOutputBinding(string invocationId) + { + StatusResult statusResult = new StatusResult() + { + Status = StatusResult.Types.Status.Success + }; + + ParameterBinding parameterBinding = new ParameterBinding() + { + Name = "output1", + Data = null + }; + + InvocationResponse invocationResponse = new InvocationResponse() + { + InvocationId = invocationId == null ? "TestInvocationId" : invocationId, + Result = statusResult + }; + + invocationResponse.OutputData.Add(parameterBinding); + + return invocationResponse; + } + private static FunctionMetadata BuildFunctionMetadataForHttpTrigger(string name, string language = null) { var functionMetadata = new FunctionMetadata() { Name = name, Language = language };