Updating metadata generation to create the $return binding for all http trigger functions. (#2579)

This commit is contained in:
Shyju Krishnankutty 2024-07-10 07:24:25 -07:00 коммит произвёл GitHub
Родитель d6dfbaeaec
Коммит 228018ba39
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 277 добавлений и 4 удалений

Просмотреть файл

@ -413,7 +413,9 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
}
if (!SymbolEqualityComparer.Default.Equals(returnTypeSymbol, _knownTypes.VoidType) &&
!SymbolEqualityComparer.Default.Equals(returnTypeSymbol.OriginalDefinition, _knownTypes.TaskType))
!SymbolEqualityComparer.Default.Equals(returnTypeSymbol.OriginalDefinition, _knownTypes.TaskType) ||
// For HTTP triggers, include the return binding even if the return type is void or Task.
hasHttpTrigger)
{
// If there is a Task<T> return type, inspect T, the inner type.
if (SymbolEqualityComparer.Default.Equals(returnTypeSymbol.OriginalDefinition, _knownTypes.TaskOfTType))
@ -509,7 +511,7 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
foundHttpOutput = true;
bindingsList.Add(GetHttpReturnBinding(prop.Name));
}
else
else if (bindingAttributes.Any())
{
if (!TryCreateBindingDictionary(bindingAttributes.FirstOrDefault(), prop.Name, prop.Locations.FirstOrDefault(), out IDictionary<string, object>? bindings))
{
@ -537,7 +539,7 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
var attributes = prop.GetAttributes();
foreach (var attribute in attributes)
{
if (attribute.AttributeClass is not null &&
if (attribute.AttributeClass is not null &&
attribute.AttributeClass.IsOrDerivedFrom(_knownFunctionMetadataTypes.HttpResultAttribute))
{
return true;
@ -625,7 +627,7 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
{
if (IsArrayOrNotNull(namedArgument.Value))
{
if (string.Equals(namedArgument.Key, Constants.FunctionMetadataBindingProps.IsBatchedKey)
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

Просмотреть файл

@ -11,3 +11,4 @@
### Microsoft.Azure.Functions.Worker.Sdk.Generators 1.3.1
- ExtensionStartupRunnerGenerator generating code which conflicts with customer code (namespace) (#2542)
- Enhanced function metadata generation to include `$return` binding for HTTP trigger functions. (#1619)

Просмотреть файл

@ -100,6 +100,7 @@ namespace TestProject
var metadataList = new List<IFunctionMetadata>();
var Function0RawBindings = new List<string>();
Function0RawBindings.Add(@""{{""""name"""":""""req"""",""""type"""":""""httpTrigger"""",""""direction"""":""""In"""",""""authLevel"""":""""Admin"""",""""methods"""":[""""get"""",""""post""""],""""route"""":""""/api2""""}}"");
Function0RawBindings.Add(@""{{""""name"""":""""$return"""",""""type"""":""""http"""",""""direction"""":""""Out""""}}"");
var Function0 = new DefaultFunctionMetadata
{{

Просмотреть файл

@ -196,6 +196,7 @@ namespace Microsoft.Azure.Functions.SdkGeneratorTests
var metadataList = new List<IFunctionMetadata>();
var Function0RawBindings = new List<string>();
Function0RawBindings.Add(@"{""name"":""req"",""type"":""httpTrigger"",""direction"":""In"",""authLevel"":""Admin"",""methods"":[""get"",""post""],""route"":""/api2""}");
Function0RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}");
var Function0 = new DefaultFunctionMetadata
{
@ -352,6 +353,133 @@ namespace Microsoft.Azure.Functions.SdkGeneratorTests
buildPropertiesDictionary: buildPropertiesDict,
languageVersion: languageVersion);
}
[Theory]
[InlineData(LanguageVersion.CSharp7_3)]
[InlineData(LanguageVersion.CSharp8)]
[InlineData(LanguageVersion.CSharp9)]
[InlineData(LanguageVersion.CSharp10)]
[InlineData(LanguageVersion.CSharp11)]
[InlineData(LanguageVersion.Latest)]
public async void NonStaticVoidOrTaskReturnType(LanguageVersion languageVersion)
{
string inputCode = """
using System;
using System.Collections.Generic;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Azure.Functions.Worker;
using System.Threading;
using System.Threading.Tasks;
namespace Foo
{
public sealed class HttpTriggers
{
[Function("Function1")]
public void FunctionWithVoidReturnType([HttpTrigger("get")] HttpRequestData req)
{
throw new NotImplementedException();
}
[Function("Function2")]
public Task FunctionWithTaskReturnType([HttpTrigger("get")] HttpRequestData req)
{
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 MyCompany.MyProject.MyApp
{
/// <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"",""methods"":[""get""]}");
Function0RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}");
var Function0 = new DefaultFunctionMetadata
{
Language = "dotnet-isolated",
Name = "Function1",
EntryPoint = "Foo.HttpTriggers.FunctionWithVoidReturnType",
RawBindings = Function0RawBindings,
ScriptFile = "TestProject.dll"
};
metadataList.Add(Function0);
var Function1RawBindings = new List<string>();
Function1RawBindings.Add(@"{""name"":""req"",""type"":""httpTrigger"",""direction"":""In"",""methods"":[""get""]}");
Function1RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}");
var Function1 = new DefaultFunctionMetadata
{
Language = "dotnet-isolated",
Name = "Function2",
EntryPoint = "Foo.HttpTriggers.FunctionWithTaskReturnType",
RawBindings = Function1RawBindings,
ScriptFile = "TestProject.dll"
};
metadataList.Add(Function1);
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;
}
}
}
"""";
// override the namespace value for generated types using msbuild property.
var buildPropertiesDict = new Dictionary<string, string>()
{
{ Constants.BuildProperties.GeneratedCodeNamespace, "MyCompany.MyProject.MyApp"}
};
await TestHelpers.RunTestAsync<FunctionMetadataProviderGenerator>(
_referencedExtensionAssemblies,
inputCode,
expectedGeneratedFileName,
expectedOutput,
buildPropertiesDictionary: buildPropertiesDict,
languageVersion: languageVersion);
}
}
}
}

Просмотреть файл

@ -1,6 +1,7 @@
// 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.AspNetCore.Http;
@ -884,6 +885,146 @@ namespace Microsoft.Azure.Functions.SdkGeneratorTests
expectedGeneratedFileName,
expectedOutput);
}
[Theory]
[InlineData(LanguageVersion.CSharp7_3)]
[InlineData(LanguageVersion.CSharp8)]
[InlineData(LanguageVersion.CSharp9)]
[InlineData(LanguageVersion.CSharp10)]
[InlineData(LanguageVersion.CSharp11)]
[InlineData(LanguageVersion.Latest)]
public async void HttpTriggerVoidOrTaskReturnType(LanguageVersion languageVersion)
{
string inputCode = """
using System;
using System.Collections.Generic;
using Microsoft.Azure.Functions.Worker;
using Microsoft.AspNetCore.Http;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Foo
{
public sealed class HttpTriggers
{
[Function("Function1")]
public Task Foo([HttpTrigger("get")] HttpRequest r) => throw new NotImplementedException();
[Function("Function2")]
public void Bar([HttpTrigger("get")] HttpRequest req) => throw new NotImplementedException();
[Obsolete("This method is obsolete. Use Foo instead.")]
[Function("Function3")]
public Task Baz([HttpTrigger("get")] HttpRequest r) => 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 MyCompany.MyProject.MyApp
{
/// <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"":""r"",""type"":""httpTrigger"",""direction"":""In"",""methods"":[""get""]}");
Function0RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}");
var Function0 = new DefaultFunctionMetadata
{
Language = "dotnet-isolated",
Name = "Function1",
EntryPoint = "Foo.HttpTriggers.Foo",
RawBindings = Function0RawBindings,
ScriptFile = "TestProject.dll"
};
metadataList.Add(Function0);
var Function1RawBindings = new List<string>();
Function1RawBindings.Add(@"{""name"":""req"",""type"":""httpTrigger"",""direction"":""In"",""methods"":[""get""]}");
Function1RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}");
var Function1 = new DefaultFunctionMetadata
{
Language = "dotnet-isolated",
Name = "Function2",
EntryPoint = "Foo.HttpTriggers.Bar",
RawBindings = Function1RawBindings,
ScriptFile = "TestProject.dll"
};
metadataList.Add(Function1);
var Function2RawBindings = new List<string>();
Function2RawBindings.Add(@"{""name"":""r"",""type"":""httpTrigger"",""direction"":""In"",""methods"":[""get""]}");
Function2RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}");
var Function2 = new DefaultFunctionMetadata
{
Language = "dotnet-isolated",
Name = "Function3",
EntryPoint = "Foo.HttpTriggers.Baz",
RawBindings = Function2RawBindings,
ScriptFile = "TestProject.dll"
};
metadataList.Add(Function2);
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;
}
}
}
"""";
// override the namespace value for generated types using msbuild property.
var buildPropertiesDict = new Dictionary<string, string>()
{
{ Constants.BuildProperties.GeneratedCodeNamespace, "MyCompany.MyProject.MyApp"}
};
await TestHelpers.RunTestAsync<FunctionMetadataProviderGenerator>(
_referencedExtensionAssemblies,
inputCode,
expectedGeneratedFileName,
expectedOutput,
buildPropertiesDictionary: buildPropertiesDict,
languageVersion: languageVersion);
}
}
}
}