Check if Azure Function method is async before adding output binding (#1297)

* check async before adding output binding

* nit
This commit is contained in:
Lucy Zhang
2021-11-11 15:48:44 -08:00
committed by GitHub
parent 8487703c21
commit aa902b78d8
7 changed files with 174 additions and 5 deletions

View File

@@ -64,7 +64,7 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions
}
MethodDeclarationSyntax azureFunction = matchingMethods.First();
var newParam = this.Parameters.bindingType == BindingType.input ? this.GenerateInputBinding() : this.GenerateOutputBinding();
var newParam = this.Parameters.bindingType == BindingType.input ? this.GenerateInputBinding() : this.GenerateOutputBinding(azureFunction);
// Generate updated method with the new parameter
// normalizewhitespace gets rid of any newline whitespace in the leading trivia, so we add that back
@@ -128,8 +128,12 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions
/// <summary>
/// Generates a parameter for the sql output binding that looks like
/// [Sql("[dbo].[table1]", ConnectionStringSetting = "SqlConnectionString")] out Object output
/// if the Azure Function method is not async and
/// [Sql("[dbo].[table1]", ConnectionStringSetting = "SqlConnectionString")] IAsyncCollector<Object> output
/// if the Azure Function method is async.
/// <param name="azureFunction"> Azure Function method. </param>
/// </summary>
private ParameterSyntax GenerateOutputBinding()
private ParameterSyntax GenerateOutputBinding(MethodDeclarationSyntax azureFunction)
{
// Create arguments for the Sql Output Binding attribute
var argumentList = SyntaxFactory.AttributeArgumentList();
@@ -140,9 +144,22 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions
attributesList = attributesList.Add(SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList<AttributeSyntax>(SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Sql")).WithArgumentList(argumentList))));
var syntaxTokenList = new SyntaxTokenList();
syntaxTokenList = syntaxTokenList.Add(SyntaxFactory.Token(SyntaxKind.OutKeyword));
TypeSyntax typeSyntax;
ParameterSyntax newParam = SyntaxFactory.Parameter(attributesList, syntaxTokenList, SyntaxFactory.ParseTypeName(typeof(Object).Name), SyntaxFactory.Identifier("output"), null);
// Check if Azure Function method is async
IEnumerable<SyntaxToken> asyncModifier = azureFunction.Modifiers.Where(m => m.ToString() == "async");
bool asyncMethod = asyncModifier.Count() > 0;
if (asyncMethod)
{
typeSyntax = SyntaxFactory.ParseTypeName("IAsyncCollector<Object>");
} else
{
syntaxTokenList = syntaxTokenList.Add(SyntaxFactory.Token(SyntaxKind.OutKeyword));
typeSyntax = SyntaxFactory.ParseTypeName(typeof(Object).Name);
}
ParameterSyntax newParam = SyntaxFactory.Parameter(attributesList, syntaxTokenList, typeSyntax, SyntaxFactory.Identifier("output"), null);
return newParam;
}
}

View File

@@ -55,5 +55,20 @@ namespace Company.Namespace
// Spec Defines: HTTP 400
throw new NotImplementedException();
}
/// <summary> Lets a user post new artists. </summary>
/// <param name="body"> The Artists to use. </param>
/// <param name="req"> Raw HTTP Request. </param>
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
/// <exception cref="ArgumentNullException"> <paramref name="body"/> is null. </exception>
[FunctionName("NewArtists_post")]
public async IActionResult NewArtists([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req)
{
_logger.LogInformation("HTTP trigger function processed a request.");
// TODO: Handle Documented Responses.
// Spec Defines: HTTP 200
// Spec Defines: HTTP 400
throw new NotImplementedException();
}
}
}

View File

@@ -54,5 +54,20 @@ namespace Company.Namespace
// Spec Defines: HTTP 400
throw new NotImplementedException();
}
/// <summary> Lets a user post new artists. </summary>
/// <param name="body"> The Artists to use. </param>
/// <param name="req"> Raw HTTP Request. </param>
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
/// <exception cref="ArgumentNullException"> <paramref name="body"/> is null. </exception>
[FunctionName("NewArtists_post")]
public async IActionResult NewArtists([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req)
{
_logger.LogInformation("HTTP trigger function processed a request.");
// TODO: Handle Documented Responses.
// Spec Defines: HTTP 200
// Spec Defines: HTTP 400
throw new NotImplementedException();
}
}
}

View File

@@ -54,5 +54,20 @@ namespace Company.Namespace
// Spec Defines: HTTP 400
throw new NotImplementedException();
}
/// <summary> Lets a user post new artists. </summary>
/// <param name="body"> The Artists to use. </param>
/// <param name="req"> Raw HTTP Request. </param>
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
/// <exception cref="ArgumentNullException"> <paramref name="body"/> is null. </exception>
[FunctionName("NewArtists_post")]
public async IActionResult NewArtists([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req)
{
_logger.LogInformation("HTTP trigger function processed a request.");
// TODO: Handle Documented Responses.
// Spec Defines: HTTP 200
// Spec Defines: HTTP 400
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Company.Namespace.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
namespace Company.Namespace
{
public class ArtistsApi
{
private ILogger<ArtistsApi> _logger;
/// <summary> Initializes a new instance of ArtistsApi. </summary>
/// <param name="logger"> Class logger. </param>
/// <exception cref="ArgumentNullException"> <paramref name="logger"/> is null. </exception>
public ArtistsApi(ILogger<ArtistsApi> logger)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger;
}
/// <summary> Returns a list of artists. </summary>
/// <param name="req"> Raw HTTP Request. </param>
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
[FunctionName("GetArtists_get")]
public IActionResult GetArtists([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "artists")] HttpRequest req)
{
_logger.LogInformation("HTTP trigger function processed a request.");
// TODO: Handle Documented Responses.
// Spec Defines: HTTP 200
// Spec Defines: HTTP 400
throw new NotImplementedException();
}
/// <summary> Lets a user post a new artist. </summary>
/// <param name="body"> The Artist to use. </param>
/// <param name="req"> Raw HTTP Request. </param>
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
/// <exception cref="ArgumentNullException"> <paramref name="body"/> is null. </exception>
[FunctionName("NewArtist_post")]
public IActionResult NewArtist([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req)
{
_logger.LogInformation("HTTP trigger function processed a request.");
// TODO: Handle Documented Responses.
// Spec Defines: HTTP 200
// Spec Defines: HTTP 400
throw new NotImplementedException();
}
/// <summary> Lets a user post new artists. </summary>
/// <param name="body"> The Artists to use. </param>
/// <param name="req"> Raw HTTP Request. </param>
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
/// <exception cref="ArgumentNullException"> <paramref name="body"/> is null. </exception>
[FunctionName("NewArtists_post")]
public async IActionResult NewArtists([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req, [Sql("[dbo].[table1]", ConnectionStringSetting = "SqlConnectionString")] IAsyncCollector<Object> output)
{
_logger.LogInformation("HTTP trigger function processed a request.");
// TODO: Handle Documented Responses.
// Spec Defines: HTTP 200
// Spec Defines: HTTP 400
throw new NotImplementedException();
}
}
}

View File

@@ -76,6 +76,37 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions
Assert.AreEqual(expectedFileText, actualFileText);
}
/// <summary>
/// Verify output binding gets added for an async method
/// </summary>
[Test]
public void AddSqlOutputBindingAsync()
{
// copy the original file because the output binding will be inserted into the file
string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNoBindings.cs");
string testFile = Path.Join(Path.GetTempPath(), $"InsertSqlOutputBindingAsync-{DateTime.Now.ToString("yyyy - dd - MM--HH - mm - ss")}.cs");
File.Copy(originalFile, testFile, true);
AddSqlBindingParams parameters = new AddSqlBindingParams
{
bindingType = BindingType.output,
filePath = testFile,
functionName = "NewArtists_post",
objectName = "[dbo].[table1]",
connectionStringSetting = "SqlConnectionString"
};
AddSqlBindingOperation operation = new AddSqlBindingOperation(parameters);
ResultStatus result = operation.AddBinding();
Assert.True(result.Success);
Assert.IsNull(result.ErrorMessage);
string expectedFileText = File.ReadAllText(Path.Join(testAzureFunctionsFolder, "AzureFunctionsOutputBindingAsync.cs"));
string actualFileText = File.ReadAllText(testFile);
Assert.AreEqual(expectedFileText, actualFileText);
}
/// <summary>
/// Verify what happens when specified azure function isn't found
/// </summary>
@@ -150,9 +181,10 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions
Assert.True(result.Success);
Assert.Null(result.ErrorMessage);
Assert.AreEqual(2, result.azureFunctions.Length);
Assert.AreEqual(3, result.azureFunctions.Length);
Assert.AreEqual(result.azureFunctions[0], "GetArtists_get");
Assert.AreEqual(result.azureFunctions[1], "NewArtist_post");
Assert.AreEqual(result.azureFunctions[2], "NewArtists_post");
}
/// <summary>

View File

@@ -44,6 +44,7 @@
<Compile Remove="AzureFunctions\AzureFunctionTestFiles\AzureFunctionsNoBindings.cs" />
<Compile Remove="AzureFunctions\AzureFunctionTestFiles\AzureFunctionsOutputBinding.cs" />
<Compile Remove="AzureFunctions\AzureFunctionTestFiles\AzureFunctionsNet5.cs" />
<Compile Remove="AzureFunctions\AzureFunctionTestFiles\AzureFunctionsOutputBindingAsync.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="DacFx\Dacpacs\" />