Skip to content

Commit

Permalink
[Source-Gen][Bug Fix] Array input in SignalR generated correctly (#2344)
Browse files Browse the repository at this point in the history
  • Loading branch information
satvu authored Apr 19, 2024
1 parent ab611b2 commit 1e81e61
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -623,9 +623,10 @@ private bool TryGetAttributeProperties(AttributeData attributeData, Location? at

foreach (var namedArgument in attributeData.NamedArguments)
{
if (namedArgument.Value.Value != null)
if (IsArrayOrNotNull(namedArgument.Value))
{
if (string.Equals(namedArgument.Key, Constants.FunctionMetadataBindingProps.IsBatchedKey) && !attrProperties.ContainsKey("cardinality"))
if (string.Equals(namedArgument.Key, Constants.FunctionMetadataBindingProps.IsBatchedKey)
&& !attrProperties.ContainsKey("cardinality") && namedArgument.Value.Value != null)
{
var argValue = (bool)namedArgument.Value.Value; // isBatched only takes in booleans and the generator will parse it as a bool so we can type cast this to use in the next line

Expand Down Expand Up @@ -672,6 +673,24 @@ private bool TryGetAttributeProperties(AttributeData attributeData, Location? at
return true;
}

private static bool IsArrayOrNotNull(TypedConstant? namedArgument)
{
if (namedArgument is null)
{
return false;
}

// special handling required for array types which store values differently (cannot check namedArgument.Value.Value)
if (namedArgument.Value.Kind is TypedConstantKind.Array)
{
return true;
}
else
{
return namedArgument.Value.Value != null; // similar to legacy generator, arguments with null values are not written to function metadata
}
}

private bool TryLoadConstructorArguments(AttributeData attributeData, IDictionary<string, object?> arguments, Location? attributeLocation)
{
IMethodSymbol? attribMethodSymbol = attributeData.AttributeConstructor;
Expand Down
2 changes: 2 additions & 0 deletions sdk/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ public class MyOutputType
public IActionResult HttpResponse { get; set; }
}
```

- Fix bug causing compiler error when named arguments in function attributes are array types (#2344).
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker.Sdk.Generators;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Xunit;

namespace Microsoft.Azure.Functions.SdkGeneratorTests
{
public partial class FunctionMetadataProviderGeneratorTests
{
public class SignalRTests
{
private readonly Assembly[] _referencedExtensionAssemblies;

public SignalRTests()
{
var abstractionsExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Abstractions.dll");
var hostingExtension = typeof(HostBuilder).Assembly;
var diExtension = typeof(DefaultServiceProviderFactory).Assembly;
var hostingAbExtension = typeof(IHost).Assembly;
var diAbExtension = typeof(IServiceCollection).Assembly;
var signalRExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.SignalRService.dll");
var httpExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Http.dll");

_referencedExtensionAssemblies = new[]
{
abstractionsExtension,
hostingExtension,
hostingAbExtension,
diExtension,
diAbExtension,
signalRExtension,
httpExtension
};
}

[Theory]
[InlineData(LanguageVersion.CSharp7_3)]
[InlineData(LanguageVersion.Latest)]
public async Task SignalRFunctionWithClaimsList(LanguageVersion languageVersion)
{
string inputCode = """
using System;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
namespace FunctionApp
{
public class SignalRFunction
{
[Function("negotiate")]
public string Negotiate([HttpTrigger(AuthorizationLevel.Function)] HttpRequestData req, [SignalRConnectionInfoInput(HubName = "TestFunctions", UserId = "{headers.user}",
IdToken = "{headers.token}",
ClaimTypeList = new []{"user_id"})] string connectionInfo)
{
throw new NotImplementedException();
}
}
}
""";

string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs";
string expectedOutput = """
// <auto-generated/>
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace TestProject
{
/// <summary>
/// Custom <see cref="IFunctionMetadataProvider"/> implementation that returns function metadata definitions for the current worker."/>
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider
{
/// <inheritdoc/>
public Task<ImmutableArray<IFunctionMetadata>> GetFunctionMetadataAsync(string directory)
{
var metadataList = new List<IFunctionMetadata>();
var Function0RawBindings = new List<string>();
Function0RawBindings.Add(@"{""name"":""req"",""type"":""httpTrigger"",""direction"":""In"",""authLevel"":""Function""}");
Function0RawBindings.Add(@"{""name"":""connectionInfo"",""type"":""signalRConnectionInfo"",""direction"":""In"",""hubName"":""TestFunctions"",""userId"":""{headers.user}"",""idToken"":""{headers.token}"",""claimTypeList"":[""user_id""],""dataType"":""String""}");
Function0RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}");
var Function0 = new DefaultFunctionMetadata
{
Language = "dotnet-isolated",
Name = "negotiate",
EntryPoint = "FunctionApp.SignalRFunction.Negotiate",
RawBindings = Function0RawBindings,
ScriptFile = "TestProject.dll"
};
metadataList.Add(Function0);
return Task.FromResult(metadataList.ToImmutableArray());
}
}
/// <summary>
/// Extension methods to enable registration of the custom <see cref="IFunctionMetadataProvider"/> implementation generated for the current worker.
/// </summary>
public static class WorkerHostBuilderFunctionMetadataProviderExtension
{
///<summary>
/// Adds the GeneratedFunctionMetadataProvider to the service collection.
/// During initialization, the worker will return generated function metadata instead of relying on the Azure Functions host for function indexing.
///</summary>
public static IHostBuilder ConfigureGeneratedFunctionMetadataProvider(this IHostBuilder builder)
{
builder.ConfigureServices(s =>
{
s.AddSingleton<IFunctionMetadataProvider, GeneratedFunctionMetadataProvider>();
});
return builder;
}
}
}
""";

await TestHelpers.RunTestAsync<FunctionMetadataProviderGenerator>(
_referencedExtensionAssemblies,
inputCode,
expectedGeneratedFileName,
expectedOutput,
languageVersion: languageVersion);
}
}
}
}
1 change: 1 addition & 0 deletions test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<ProjectReference Include="..\..\extensions\Worker.Extensions.EventHubs\src\Worker.Extensions.EventHubs.csproj" />
<ProjectReference Include="..\..\extensions\Worker.Extensions.Timer\src\Worker.Extensions.Timer.csproj" />
<ProjectReference Include="..\..\extensions\Worker.Extensions.Kafka\src\Worker.Extensions.Kafka.csproj" />
<ProjectReference Include="..\..\extensions\Worker.Extensions.SignalRService\src\Worker.Extensions.SignalRService.csproj" />
<ProjectReference Include="..\DependentAssemblyWithFunctions\DependentAssemblyWithFunctions.csproj" />
</ItemGroup>

Expand Down

0 comments on commit 1e81e61

Please sign in to comment.