in sdk/Sdk/FunctionMetadataGenerator.cs [476:579]
private static ExpandoObject BuildBindingMetadataFromAttribute(CustomAttribute attribute, string bindingType, TypeReference parameterType, string? parameterName)
{
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;
// 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(attribute, parameterType) && direction != Constants.OutputBindingDirection)
{
bindingProperties.Add(Constants.SupportsDeferredBindingProperty, Boolean.TrueString);
}
else
{
// 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);
foreach (var property in attribute.GetAllDefinedProperties())
{
var propName = property.Key;
var propertyName = bindingNameAliasDict.TryGetValue(property.Key, out var overriddenPropertyName)
? overriddenPropertyName
: property.Key;
bindingDict.Add(propertyName, property.Value);
}
// Determine if we should set the "Cardinality" property based on
// 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
// 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"
if (bindingDict.TryGetValue(Constants.IsBatchedKey, out object isBatchedValue)
&& isBatchedValue is bool isBatched)
{
// Batching set to true
if (isBatched)
{
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
// scenarios result in runtime failures.
if (IsIterableCollection(parameterType, out DataType dataType))
{
if (dataType.Equals(DataType.String))
{
bindingDict["DataType"] = nameof(DataType.String);
}
else if (dataType.Equals(DataType.Binary))
{
bindingDict["DataType"] = nameof(DataType.Binary);
}
}
else
{
throw new FunctionsMetadataGenerationException("Function is configured to process events in batches but parameter type is not iterable. " +
$"Change parameter named '{parameterName}' to be an IEnumerable type or set 'IsBatched' to false on your '{attribute.AttributeType.Name.Replace("Attribute", "")}' attribute.");
}
}
// Batching set to false
else
{
bindingDict["Cardinality"] = "One";
}
bindingDict.Remove(Constants.IsBatchedKey);
}
bindingDict["Properties"] = bindingProperties;
return binding;
}