mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
Check if Azure Function method is async before adding output binding (#1297)
* check async before adding output binding * nit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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\" />
|
||||
|
||||
Reference in New Issue
Block a user