Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support sdk-type binding reference type #1107

Merged
merged 13 commits into from
Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,24 @@ public class ExtensionInformationAttribute : Attribute

public bool EnableImplicitRegistration { get; }

public bool SupportsDeferredBinding { get; }
surgupta-msft marked this conversation as resolved.
Show resolved Hide resolved

public ExtensionInformationAttribute(string extensionPackage, string extensionVersion)
: this(extensionPackage, extensionVersion, false)
: this(extensionPackage, extensionVersion, false, false)
{
}

public ExtensionInformationAttribute(string extensionPackage, string extensionVersion, bool enableImplicitRegistration)
: this(extensionPackage, extensionVersion, enableImplicitRegistration, false)
{
}

public ExtensionInformationAttribute(string extensionPackage, string extensionVersion, bool enableImplicitRegistration, bool supportsDeferredBinding)
{
ExtensionPackage = extensionPackage;
ExtensionVersion = extensionVersion;
EnableImplicitRegistration = enableImplicitRegistration;
SupportsDeferredBinding = supportsDeferredBinding;
}
}
}
13 changes: 13 additions & 0 deletions pack-sdk.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# FOR TESTING - WILL DELETE
surgupta-msft marked this conversation as resolved.
Show resolved Hide resolved

surgupta-msft marked this conversation as resolved.
Show resolved Hide resolved
$packageSuffix = "dev" + [datetime]::UtcNow.Ticks.ToString()
$outputDirectory = "../../buildoutput"
$project = "sdk/Sdk/Sdk.csproj"

dotnet --version

dotnet build

$cmd = "pack", "$project", "-o", $outputDirectory, "--no-build", "--version-suffix", "-$packageSuffix"

& dotnet $cmd
7 changes: 7 additions & 0 deletions sdk/Sdk/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,12 @@ internal static class Constants
internal const string NetCoreVersion6 = "v6.0";
internal const string NetCoreVersion31 = "v3.1";
internal const string AzureFunctionsVersion3 = "v3";

// Binding directions
internal const string OutputBindingDirection = "Out";
internal const string InputBindingDirection = "In";

// Binding properties
internal const string SupportsDeferredBindingProperty = "SupportsDeferredBinding";
}
}
94 changes: 56 additions & 38 deletions sdk/Sdk/FunctionMetadataGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Mono.Cecil;
using Mono.Collections.Generic;

namespace Microsoft.Azure.Functions.Worker.Sdk
{
Expand Down Expand Up @@ -118,7 +120,7 @@ internal IEnumerable<SdkFunctionMetadata> GenerateFunctionMetadata(ModuleDefinit
functions.AddRange(functionsResult);
}

if (!moduleExtensionRegistered && TryAddExtensionInfo(_extensions, module.Assembly, usedByFunction: false))
if (!moduleExtensionRegistered && TryAddExtensionInfo(_extensions, module.Assembly, out bool supportsDeferredBinding, usedByFunction: false))
{
_logger.LogMessage($"Implicitly registered {module.FileName} as an extension.");
}
Expand All @@ -145,10 +147,8 @@ private void AddFunctionMetadataIfFunction(IList<SdkFunctionMetadata> functions,
{
try
{

var allBindings = CreateBindingMetadataAndAddExtensions(method);


foreach (var binding in allBindings)
{
metadata.Bindings.Add(binding);
Expand All @@ -158,7 +158,7 @@ private void AddFunctionMetadataIfFunction(IList<SdkFunctionMetadata> functions,
}
catch (FunctionsMetadataGenerationException ex)
{
throw new FunctionsMetadataGenerationException($"Failed to generate medata for function '{metadata.Name}' (method '{method.FullName}'): {ex.Message}");
throw new FunctionsMetadataGenerationException($"Failed to generate metadata for function '{metadata.Name}' (method '{method.FullName}'): {ex.Message}");
}
}
}
Expand Down Expand Up @@ -222,7 +222,6 @@ private static SdkFunctionMetadata CreateSdkFunctionMetadata(string functionName
}
};


return function;
}

Expand Down Expand Up @@ -288,8 +287,8 @@ private void AddOutputBindingsFromReturnType(IList<ExpandoObject> bindingMetadat

bool hasOutputModel = TryAddOutputBindingsFromProperties(bindingMetadata, returnDefinition);

// Special handling for HTTP results using POCOs/Types other
// than HttpResponseData. We should improve this to expand this
// Special handling for HTTP results using POCOs/Types other
// than HttpResponseData. We should improve this to expand this
// support to other triggers without special handling
if (!hasOutputModel && bindingMetadata.Any(d => IsHttpTrigger(d)))
{
Expand Down Expand Up @@ -348,7 +347,7 @@ private void AddOutputBindingFromProperty(IList<ExpandoObject> bindingMetadata,
foundOutputAttribute = true;

AddOutputBindingMetadata(bindingMetadata, propertyAttribute, property.PropertyType, property.Name);
AddExtensionInfo(_extensions, propertyAttribute);
AddExtensionInfo(_extensions, propertyAttribute, out bool supportsDeferredBinding);
}
}
}
Expand All @@ -368,7 +367,7 @@ private bool TryAddOutputBindingFromMethod(IList<ExpandoObject> bindingMetadata,
}

AddOutputBindingMetadata(bindingMetadata, methodAttribute, methodAttribute.AttributeType, Constants.ReturnBindingName);
AddExtensionInfo(_extensions, methodAttribute);
AddExtensionInfo(_extensions, methodAttribute, out bool supportsDeferredBinding);

foundBinding = true;
}
Expand All @@ -385,8 +384,8 @@ private void AddInputTriggerBindingsAndExtensions(IList<ExpandoObject> bindingMe
{
if (IsFunctionBindingType(parameterAttribute))
{
AddBindingMetadata(bindingMetadata, parameterAttribute, parameter.ParameterType, parameter.Name);
AddExtensionInfo(_extensions, parameterAttribute);
AddExtensionInfo(_extensions, parameterAttribute, out bool supportsDeferredBinding);
AddBindingMetadata(bindingMetadata, parameterAttribute, parameter.ParameterType, parameter.Name, supportsDeferredBinding);
}
}
}
Expand Down Expand Up @@ -417,11 +416,10 @@ private static void AddOutputBindingMetadata(IList<ExpandoObject> bindingMetadat
AddBindingMetadata(bindingMetadata, attribute, parameterType, parameterName: name);
}

private static void AddBindingMetadata(IList<ExpandoObject> bindingMetadata, CustomAttribute attribute, TypeReference parameterType, string? parameterName)
private static void AddBindingMetadata(IList<ExpandoObject> bindingMetadata, CustomAttribute attribute, TypeReference parameterType, string? parameterName, bool supportsReferenceType = false)
{
string bindingType = GetBindingType(attribute);

ExpandoObject binding = BuildBindingMetadataFromAttribute(attribute, bindingType, parameterType, parameterName);
ExpandoObject binding = BuildBindingMetadataFromAttribute(attribute, bindingType, parameterType, parameterName, supportsReferenceType);
bindingMetadata.Add(binding);
}

Expand Down Expand Up @@ -473,29 +471,40 @@ private static void AddBindingMetadata(IList<ExpandoObject> bindingMetadata, Cus
return bindingNameAliasMap;
}

private static ExpandoObject BuildBindingMetadataFromAttribute(CustomAttribute attribute, string bindingType, TypeReference parameterType, string? parameterName)
private static ExpandoObject BuildBindingMetadataFromAttribute(CustomAttribute attribute, string bindingType, TypeReference parameterType, string? parameterName, bool supportsDeferredBinding)
{
ExpandoObject binding = new ExpandoObject();

var bindingDict = (IDictionary<string, object>)binding;
var bindingProperties = new Dictionary<string, object>();

if (!string.IsNullOrEmpty(parameterName))
{
bindingDict["Name"] = parameterName!;
}

var direction = GetBindingDirection(attribute);
bindingDict["Direction"] = direction;
bindingDict["Type"] = bindingType;
bindingDict["Direction"] = GetBindingDirection(attribute);

// Is string parameter type
if (IsStringType(parameterType.FullName))
// For extensions that support deferred binding, set the supportsDeferredBinding property so parameters are bound by the worker
// Only use deferred binding for input and trigger bindings, output is out not currently supported
if (supportsDeferredBinding && direction != Constants.OutputBindingDirection)
{
bindingDict["DataType"] = "String";
bindingProperties.Add(Constants.SupportsDeferredBindingProperty, Boolean.TrueString);
}
// Is binary parameter type
else if (IsBinaryType(parameterType.FullName))
else
{
bindingDict["DataType"] = "Binary";
// Is string parameter type
if (IsStringType(parameterType.FullName))
{
bindingDict["DataType"] = nameof(DataType.String);
}
// Is binary parameter type
else if (IsBinaryType(parameterType.FullName))
{
bindingDict["DataType"] = nameof(DataType.Binary);
}
}

var bindingNameAliasDict = GetPropertyNameAliasMapping(attribute);
Expand All @@ -516,12 +525,12 @@ private static ExpandoObject BuildBindingMetadataFromAttribute(CustomAttribute a
// the presence of "IsBatched." This is a property that is from the
// attributes that implement the ISupportCardinality interface.
//
// Note that we are directly looking for "IsBatched" today while we
// Note that we are directly looking for "IsBatched" today while we
// are not actually instantiating the Attribute type and instead relying
// on type inspection via Mono.Cecil.
// TODO: Do not hard-code "IsBatched" as the property to set cardinality.
// We should rely on the interface
//
//
// Conversion rule
// "IsBatched": true => "Cardinality": "Many"
// "IsBatched": false => "Cardinality": "One"
Expand All @@ -534,17 +543,17 @@ private static ExpandoObject BuildBindingMetadataFromAttribute(CustomAttribute a
bindingDict["Cardinality"] = "Many";
// Throw if parameter type is *definitely* not a collection type.
// Note that this logic doesn't dictate what we can/can't do, and
// we can be more restrictive in the future because today some
// we can be more restrictive in the future because today some
// scenarios result in runtime failures.
if (IsIterableCollection(parameterType, out DataType dataType))
{
if (dataType.Equals(DataType.String))
{
bindingDict["DataType"] = "String";
bindingDict["DataType"] = nameof(DataType.String);
}
else if (dataType.Equals(DataType.Binary))
{
bindingDict["DataType"] = "Binary";
bindingDict["DataType"] = nameof(DataType.Binary);
}
}
else
Expand All @@ -562,12 +571,14 @@ private static ExpandoObject BuildBindingMetadataFromAttribute(CustomAttribute a
bindingDict.Remove(Constants.IsBatchedKey);
}

bindingDict["Properties"] = bindingProperties;

return binding;
}

private static bool IsIterableCollection(TypeReference type, out DataType dataType)
{
// Array and not byte array
// Array and not byte array
bool isArray = type.IsArray && !string.Equals(type.FullName, Constants.ByteArrayType, StringComparison.Ordinal);
if (isArray)
{
Expand Down Expand Up @@ -649,7 +660,7 @@ private static bool IsSubclassOf(TypeDefinition definition, string interfaceFull

private static string? ResolveIEnumerableOfTType(TypeReference type, Dictionary<string, string> foundMapping)
{
// Base case:
// Base case:
// We are at IEnumerable<T> and want to return the most recent resolution of T
// (Most recent is relative to IEnumerable<T>)
if (string.Equals(type.FullName, Constants.IEnumerableOfT, StringComparison.Ordinal))
Expand Down Expand Up @@ -684,9 +695,9 @@ private static bool IsSubclassOf(TypeDefinition definition, string interfaceFull
}

return definition.Interfaces
.Select(i => ResolveIEnumerableOfTType(i.InterfaceType, foundMapping))
.FirstOrDefault(name => name is not null)
?? ResolveIEnumerableOfTType(definition.BaseType, foundMapping);
.Select(i => ResolveIEnumerableOfTType(i.InterfaceType, foundMapping))
.FirstOrDefault(name => name is not null)
?? ResolveIEnumerableOfTType(definition.BaseType, foundMapping);
}

private static DataType GetDataTypeFromType(string fullName)
Expand Down Expand Up @@ -736,19 +747,21 @@ private static void AddHttpOutputBinding(IList<ExpandoObject> bindingMetadata, s
IDictionary<string, object> returnBinding = new ExpandoObject();
returnBinding["Name"] = name;
returnBinding["Type"] = "http";
returnBinding["Direction"] = "Out";
returnBinding["Direction"] = Constants.OutputBindingDirection;

bindingMetadata.Add((ExpandoObject)returnBinding);
}

private static void AddExtensionInfo(IDictionary<string, string> extensions, CustomAttribute attribute)
private static void AddExtensionInfo(IDictionary<string, string> extensions, CustomAttribute attribute, out bool supportsDeferredBinding)
{
AssemblyDefinition extensionAssemblyDefinition = attribute.AttributeType.Resolve().Module.Assembly;
TryAddExtensionInfo(extensions, extensionAssemblyDefinition);
TryAddExtensionInfo(extensions, extensionAssemblyDefinition, out supportsDeferredBinding);
}

private static bool TryAddExtensionInfo(IDictionary<string, string> extensions, AssemblyDefinition extensionAssemblyDefinition, bool usedByFunction = true)
private static bool TryAddExtensionInfo(IDictionary<string, string> extensions, AssemblyDefinition extensionAssemblyDefinition, out bool supportsDeferredBinding, bool usedByFunction = true)
{
supportsDeferredBinding = false;

foreach (var assemblyAttribute in extensionAssemblyDefinition.CustomAttributes)
{
if (string.Equals(assemblyAttribute.AttributeType.FullName, Constants.ExtensionsInformationType, StringComparison.Ordinal))
Expand All @@ -763,6 +776,11 @@ private static bool TryAddExtensionInfo(IDictionary<string, string> extensions,
implicitlyRegister = (bool)assemblyAttribute.ConstructorArguments[2].Value;
}

if (assemblyAttribute.ConstructorArguments.Count == 4)
mathewc marked this conversation as resolved.
Show resolved Hide resolved
{
supportsDeferredBinding = (bool)assemblyAttribute.ConstructorArguments[3].Value;
}

if (usedByFunction || implicitlyRegister)
{
extensions[extensionName] = extensionVersion;
Expand All @@ -780,10 +798,10 @@ private static string GetBindingDirection(CustomAttribute attribute)
{
if (IsOutputBindingType(attribute))
{
return "Out";
return Constants.OutputBindingDirection;
}

return "In";
return Constants.InputBindingDirection;
}

private static bool IsOutputBindingType(CustomAttribute attribute)
Expand Down
33 changes: 33 additions & 0 deletions src/DotNetWorker.Core/IModelBindingData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;

namespace Microsoft.Azure.Functions.Worker.Core
{
/// <summary>
/// A representation of a Microsoft.Azure.WebJobs.ParameterBindingData
/// </summary>
public interface IModelBindingData
surgupta-msft marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// The version of the binding data content
/// </summary>
string Version { get; }

/// <summary>
/// The extension source of the binding data i.e CosmosDB, AzureStorageBlobs
/// </summary>
string Source { get; }

/// <summary>
/// The binding data content
/// </summary>
BinaryData Content { get; }

/// <summary>
/// The content type of the binding data content i.e. "application/json"
/// </summary>
string ContentType { get; }
}
}