sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs (167 lines of code) (raw):
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Threading;
using Microsoft.CodeAnalysis;
namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
{
public partial class FunctionMetadataProviderGenerator
{
internal sealed class Emitter
{
private static readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
};
public static string Emit(GeneratorExecutionContext context, IReadOnlyList<GeneratorFunctionMetadata> funcMetadata, bool includeAutoRegistrationCode)
{
string functionMetadataInfo = AddFunctionMetadataInfo(funcMetadata, context.CancellationToken);
return $$"""
// <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 {{FunctionsUtil.GetNamespaceForGeneratedCode(context)}}
{
/// <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)]
{{Constants.GeneratedCodeAttribute}}
public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider
{
/// <inheritdoc/>
public Task<ImmutableArray<IFunctionMetadata>> GetFunctionMetadataAsync(string directory)
{
var metadataList = new List<IFunctionMetadata>();
{{functionMetadataInfo}}
return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray());
}
}
/// <summary>
/// Extension methods to enable registration of the custom <see cref="IFunctionMetadataProvider"/> implementation generated for the current worker.
/// </summary>
{{Constants.GeneratedCodeAttribute}}
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;
}
}{{GetAutoConfigureStartupClass(includeAutoRegistrationCode)}}
}
""";
}
private static string GetAutoConfigureStartupClass(bool includeAutoRegistrationCode)
{
if (includeAutoRegistrationCode)
{
string result = $$"""
/// <summary>
/// Auto startup class to register the custom <see cref="IFunctionMetadataProvider"/> implementation generated for the current worker.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]
{{Constants.GeneratedCodeAttribute}}
public class FunctionMetadataProviderAutoStartup : global::Microsoft.Azure.Functions.Worker.IAutoConfigureStartup
{
/// <summary>
/// Configures the <see cref="IHostBuilder"/> to use the custom <see cref="IFunctionMetadataProvider"/> implementation generated for the current worker.
/// </summary>
/// <param name="hostBuilder">The <see cref="IHostBuilder"/> instance to use for service registration.</param>
public void Configure(IHostBuilder hostBuilder)
{
hostBuilder.ConfigureGeneratedFunctionMetadataProvider();
}
}
""";
return result;
}
return "";
}
private static string AddFunctionMetadataInfo(IReadOnlyList<GeneratorFunctionMetadata> functionMetadata, CancellationToken cancellationToken)
{
var functionCount = 0;
var builder = new StringBuilder();
foreach (var function in functionMetadata)
{
cancellationToken.ThrowIfCancellationRequested();
// we're going to base variable names on Function[Num] because some function names have characters we can't use for a dotnet variable
var functionVariableName = "Function" + functionCount.ToString();
var functionBindingsListVarName = functionVariableName + "RawBindings";
var bindingInfo = BuildBindingInfo(functionBindingsListVarName, function.RawBindings);
builder.AppendLine(
$$"""
var {{functionBindingsListVarName}} = new List<string>();
{{bindingInfo}}
var {{functionVariableName}} = new DefaultFunctionMetadata
{
Language = "{{Constants.Languages.DotnetIsolated}}",
Name = "{{function.Name}}",
EntryPoint = "{{function.EntryPoint}}",
RawBindings = {{functionBindingsListVarName}},
""");
builder.Append(BuildRetryOptions(function.Retry));
builder.AppendLine($$"""
ScriptFile = "{{function.ScriptFile}}"
""");
builder.AppendLine($$""" };""");
builder.AppendLine($$""" metadataList.Add({{functionVariableName}});""");
functionCount++;
}
return builder.ToString();
}
private static string BuildBindingInfo(string bindingListVariableName, IList<IDictionary<string, object>> bindings)
{
var builder = new StringBuilder();
foreach (var binding in bindings)
{
var jsonBinding = JsonSerializer.Serialize(binding, _jsonOptions);
builder.AppendLine($""" {bindingListVariableName}.Add(@"{jsonBinding.Replace("\"", "\"\"")}");""");
}
return builder.ToString();
}
private static StringBuilder BuildRetryOptions(GeneratorRetryOptions? retry)
{
var builder = new StringBuilder();
if (retry?.Strategy is RetryStrategy.FixedDelay)
{
builder.AppendLine($$"""
Retry = new DefaultRetryOptions
{
MaxRetryCount = {{retry!.MaxRetryCount}},
DelayInterval = TimeSpan.Parse("{{retry.DelayInterval}}")
},
""");
}
else if (retry?.Strategy is RetryStrategy.ExponentialBackoff)
{
builder.AppendLine($$"""
Retry = new DefaultRetryOptions
{
MaxRetryCount = {{retry!.MaxRetryCount}},
MinimumInterval = TimeSpan.Parse("{{retry.MinimumInterval}}"),
MaximumInterval = TimeSpan.Parse("{{retry.MaximumInterval}}")
},
""");
}
return builder;
}
}
}
}