Updating metadata generation to create the $return binding for all http trigger functions. (#2579)
This commit is contained in:
Родитель
d6dfbaeaec
Коммит
228018ba39
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче