Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.WebJobs.Description;
using Microsoft.Azure.WebJobs.Extensions.CosmosDB.Bindings;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Config;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
Expand All @@ -20,7 +20,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.CosmosDB
/// <summary>
/// Defines the configuration options for the CosmosDB binding.
/// </summary>
[Extension("CosmosDB")]
[Extension(Constants.WebJobsCosmosExtensionName)]
internal class CosmosDBExtensionConfigProvider : IExtensionConfigProvider
{
private readonly ICosmosDBServiceFactory _cosmosDBServiceFactory;
Expand All @@ -30,10 +30,10 @@ internal class CosmosDBExtensionConfigProvider : IExtensionConfigProvider
private readonly ILoggerFactory _loggerFactory;

public CosmosDBExtensionConfigProvider(
IOptions<CosmosDBOptions> options,
ICosmosDBServiceFactory cosmosDBServiceFactory,
IOptions<CosmosDBOptions> options,
ICosmosDBServiceFactory cosmosDBServiceFactory,
ICosmosDBSerializerFactory cosmosSerializerFactory,
INameResolver nameResolver,
INameResolver nameResolver,
ILoggerFactory loggerFactory)
{
_cosmosDBServiceFactory = cosmosDBServiceFactory;
Expand All @@ -53,12 +53,13 @@ public void Initialize(ExtensionConfigContext context)
throw new ArgumentNullException("context");
}

// Apply ValidateConnection to all on this rule.
// Apply ValidateConnection to all on this rule.
var rule = context.AddBindingRule<CosmosDBAttribute>();
rule.AddValidator(ValidateConnection);
rule.BindToCollector<DocumentOpenType>(typeof(CosmosDBCollectorBuilder<>), this);

rule.BindToInput<CosmosClient>(new CosmosDBClientBuilder(this));
rule.BindToInput<ParameterBindingData>((attr) => CreateParameterBindingData(attr));

// Enumerable inputs
rule.WhenIsNull(nameof(CosmosDBAttribute.Id))
Expand Down Expand Up @@ -87,6 +88,15 @@ internal void ValidateConnection(CosmosDBAttribute attribute, Type paramType)
}
}

internal ParameterBindingData CreateParameterBindingData(CosmosDBAttribute cosmosAttribute)
{
var cosmosDetails = new CosmosParameterBindingDataContent(cosmosAttribute);
var cosmosDetailsBinaryData = new BinaryData(cosmosDetails);
var parameterBindingData = new ParameterBindingData("1.0", Constants.WebJobsCosmosExtensionName, cosmosDetailsBinaryData, "application/json");

return parameterBindingData;
}

internal Task<IValueBinder> BindForItemAsync(CosmosDBAttribute attribute, Type type)
{
if (string.IsNullOrEmpty(attribute.Id))
Expand All @@ -109,15 +119,15 @@ internal CosmosClient GetService(string connection, string preferredLocations =
{
userAgent += _options.UserAgentSuffix;
}

CosmosClientOptions cosmosClientOptions = CosmosDBUtility.BuildClientOptions(_options.ConnectionMode, _cosmosSerializerFactory.CreateSerializer(), preferredLocations, userAgent);
return ClientCache.GetOrAdd(cacheKey, (c) => _cosmosDBServiceFactory.CreateService(connection, cosmosClientOptions));
}

internal CosmosDBContext CreateContext(CosmosDBAttribute attribute)
{
CosmosClient service = GetService(
connection: attribute.Connection ?? Constants.DefaultConnectionStringName,
connection: attribute.Connection ?? Constants.DefaultConnectionStringName,
preferredLocations: attribute.PreferredLocations);

return new CosmosDBContext
Expand Down Expand Up @@ -158,5 +168,49 @@ public override bool IsMatch(Type type, OpenTypeMatchContext context)
return base.IsMatch(type, context);
}
}

// Cannot use `new BinaryData(cosmosAttribute)` because this "may only be called on a Type for
// which Type.IsGenericParameter is true"; therefore we use our own reference type.
// Has the same properties as CosmosDBAttribute
private class CosmosParameterBindingDataContent
{
public CosmosParameterBindingDataContent()
{
}

public CosmosParameterBindingDataContent(CosmosDBAttribute attribute)
{
DatabaseName = attribute.DatabaseName;
ContainerName = attribute.ContainerName;
CreateIfNotExists = attribute.CreateIfNotExists;
Connection = attribute.Connection;
Id = attribute.Id;
PartitionKey = attribute.PartitionKey;
ContainerThroughput = attribute.ContainerThroughput;
SqlQuery = attribute.SqlQuery;
SqlQueryParameters = attribute.SqlQueryParameters is null ? default : attribute.SqlQueryParameters.ToDictionary(x => x.Item1, x => x.Item2);
PreferredLocations = attribute.PreferredLocations;
}

public string DatabaseName { get; set; }

public string ContainerName { get; set; }

public bool CreateIfNotExists { get; set; }

public string Connection { get; set; }

public string Id { get; set; }

public string PartitionKey { get; set; }

public int ContainerThroughput { get; set; }

public string SqlQuery { get; set; }

public IDictionary<string, object> SqlQueryParameters { get; set; }

public string PreferredLocations { get; set; }
}
}
}
1 change: 1 addition & 0 deletions src/WebJobs.Extensions.CosmosDB/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.CosmosDB
public static class Constants
{
public const string DefaultConnectionStringName = "CosmosDB";
public const string WebJobsCosmosExtensionName = "CosmosDB";
Copy link
Member Author

@liliankasem liliankasem Mar 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels silly to have two strings with the same value, but didn't want to rename the connection string const. We could just have one const called "CosmosDB" but that requires making changes to 29 references that use DefaultConnectionStringName. I think that's best left for a different PR

}

internal static class Events
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Triggers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,27 @@ public void Resolve_UsesDefault_Last()
Assert.True(context.Service.Endpoint.ToString().Contains("default"));
}

[Fact]
public void CreateParameterBindingData_CreatesValidParameterBindingDataObject()
{
var config = InitializeExtensionConfigProvider();

var sqlQueryParameters = new List<(string, object)>();
sqlQueryParameters.Add(("id", "1"));
var attribute = new CosmosDBAttribute("testDb", "testContainer") { Connection = "testConnection", Id = "id", SqlQueryParameters = sqlQueryParameters};
var data = @"{""DatabaseName"":""testDb"",""ContainerName"":""testContainer"",""CreateIfNotExists"":false,""Connection"":""testConnection"",""Id"":""id"",""PartitionKey"":null,""ContainerThroughput"":0,""SqlQuery"":null,""SqlQueryParameters"":{""id"":""1""},""PreferredLocations"":null}";
var expectedBinaryData = new BinaryData(data).ToString();

// Act
var pbdObj = config.CreateParameterBindingData(attribute);

// Assert
Assert.Equal("CosmosDB", pbdObj.Source);
Assert.Equal("1.0", pbdObj.Version);
Assert.Equal(expectedBinaryData, pbdObj.Content.ToString());
Assert.Equal("application/json", pbdObj.ContentType);
}

[Theory]
[InlineData(typeof(IEnumerable<string>), true)]
[InlineData(typeof(IEnumerable<Item>), true)]
Expand Down