diff --git a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AddSqlBindingOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AddSqlBindingOperation.cs index b3b25b81..8296c156 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AddSqlBindingOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AddSqlBindingOperation.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Diagnostics; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -87,13 +88,10 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions Success = true }; } - catch (Exception e) + catch (Exception ex) { - return new ResultStatus() - { - Success = false, - ErrorMessage = e.ToString() - }; + Logger.Write(TraceEventType.Information, $"Failed to add sql binding. Error: {ex.Message}"); + throw ex; } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AzureFunctionsService.cs b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AzureFunctionsService.cs index 41aeec1d..f41b80f6 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AzureFunctionsService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AzureFunctionsService.cs @@ -33,6 +33,7 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions public void InitializeService(ServiceHost serviceHost) { serviceHost.SetRequestHandler(AddSqlBindingRequest.Type, this.HandleAddSqlBindingRequest); + serviceHost.SetRequestHandler(GetAzureFunctionsRequest.Type, this.HandleGetAzureFunctionsRequest); } /// @@ -52,5 +53,23 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions await requestContext.SendError(e); } } + + /// + /// Handles request to get the names of the Azure functions in a file + /// + public async Task HandleGetAzureFunctionsRequest(GetAzureFunctionsParams parameters, RequestContext requestContext) + { + try + { + GetAzureFunctionsOperation operation = new GetAzureFunctionsOperation(parameters); + GetAzureFunctionsResult result = operation.GetAzureFunctions(); + + await requestContext.SendResult(result); + } + catch (Exception e) + { + await requestContext.SendError(e); + } + } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/Contracts/GetAzureFunctionsRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/Contracts/GetAzureFunctionsRequest.cs new file mode 100644 index 00000000..cebf32ca --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/Contracts/GetAzureFunctionsRequest.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +using System.Collections.Generic; +using Microsoft.SqlTools.Hosting.Protocol.Contracts; +using Microsoft.SqlTools.ServiceLayer.SchemaCompare.Contracts; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions.Contracts +{ + /// + /// Parameters for getting the Azure functions in a file + /// + public class GetAzureFunctionsParams + { + /// + /// Gets or sets the filePath + /// + public string filePath { get; set; } + } + + /// + /// Parameters returned from a get Azure functions request + /// + public class GetAzureFunctionsResult : ResultStatus + { + public string[] azureFunctions { get; set; } + } + + /// + /// Defines the get Azure functions request + /// + class GetAzureFunctionsRequest + { + public static readonly RequestType Type = + RequestType.Create("azureFunctions/getAzureFunctions"); + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/GetAzureFunctionsOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/GetAzureFunctionsOperation.cs new file mode 100644 index 00000000..2ff38a98 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/GetAzureFunctionsOperation.cs @@ -0,0 +1,76 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.SqlTools.ServiceLayer.AzureFunctions.Contracts; +using Microsoft.SqlTools.Utility; +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions +{ + /// + /// Class to represent getting the Azure Functions in a file + /// + class GetAzureFunctionsOperation + { + const string functionAttributeText = "FunctionName"; + + public GetAzureFunctionsParams Parameters { get; } + + public GetAzureFunctionsOperation(GetAzureFunctionsParams parameters) + { + Validate.IsNotNull("parameters", parameters); + this.Parameters = parameters; + } + + /// + /// Gets the names of all the azure functions in a file + /// + /// the result of trying to get the names of all the Azure functions in a file + public GetAzureFunctionsResult GetAzureFunctions() + { + try + { + string text = File.ReadAllText(Parameters.filePath); + + SyntaxTree tree = CSharpSyntaxTree.ParseText(text); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + + // Look for Azure Functions in the file + // Get all method declarations + IEnumerable methodDeclarations = root.DescendantNodes().OfType(); + + // get all the method declarations with the FunctionName attribute + IEnumerable methodsWithFunctionAttributes = methodDeclarations.Where(md => md.AttributeLists.Count > 0).Where(md => md.AttributeLists.Where(a => a.Attributes.Where(attr => attr.Name.ToString().Contains(functionAttributeText)).Count() == 1).Count() == 1); + + // Get FunctionName attributes + IEnumerable functionNameAttributes = methodsWithFunctionAttributes.Select(md => md.AttributeLists.Select(a => a.Attributes.Where(attr => attr.Name.ToString().Contains(functionAttributeText)).First()).First()); + + // Get the function names in the FunctionName attributes + IEnumerable nameArgs = functionNameAttributes.Select(a => a.ArgumentList.Arguments.First()); + + // Remove quotes from around the names + string[] aFNames = nameArgs.Select(ab => ab.ToString().Trim('\"')).ToArray(); + + return new GetAzureFunctionsResult() + { + Success = true, + azureFunctions = aFNames + }; + } + catch (Exception ex) + { + Logger.Write(TraceEventType.Information, $"Failed to get Azure functions. Error: {ex.Message}"); + throw ex; + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsInputBinding.ts b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsInputBinding.cs similarity index 100% rename from test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsInputBinding.ts rename to test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsInputBinding.cs diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsMultipleSameFunction.ts b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsMultipleSameFunction.cs similarity index 100% rename from test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsMultipleSameFunction.ts rename to test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsMultipleSameFunction.cs diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsNoBindings.ts b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsNoBindings.cs similarity index 100% rename from test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsNoBindings.ts rename to test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsNoBindings.cs diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsOutputBinding.ts b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsOutputBinding.cs similarity index 100% rename from test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsOutputBinding.ts rename to test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsOutputBinding.cs diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionsServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionsServiceTests.cs index aaf86c1d..471bb6b2 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionsServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionsServiceTests.cs @@ -21,8 +21,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions public void AddSqlInputBinding() { // copy the original file because the input binding will be inserted into the file - string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNoBindings.ts"); - string testFile = Path.Join(Path.GetTempPath(), string.Format("InsertSqlInputBinding-{0}.ts", DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss"))); + string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNoBindings.cs"); + string testFile = Path.Join(Path.GetTempPath(), $"InsertSqlInputBinding-{DateTime.Now.ToString("yyyy - dd - MM--HH - mm - ss")}.cs"); File.Copy(originalFile, testFile, true); AddSqlBindingParams parameters = new AddSqlBindingParams @@ -40,7 +40,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions Assert.True(result.Success); Assert.IsNull(result.ErrorMessage); - string expectedFileText = File.ReadAllText(Path.Join(testAzureFunctionsFolder, "AzureFunctionsInputBinding.ts")); + string expectedFileText = File.ReadAllText(Path.Join(testAzureFunctionsFolder, "AzureFunctionsInputBinding.cs")); string actualFileText = File.ReadAllText(testFile); Assert.AreEqual(expectedFileText, actualFileText); } @@ -52,8 +52,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions public void AddSqlOutputBinding() { // copy the original file because the output binding will be inserted into the file - string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNoBindings.ts"); - string testFile = Path.Join(Path.GetTempPath(), string.Format("InsertSqlOutputBinding-{0}.ts", DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss"))); + string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNoBindings.cs"); + string testFile = Path.Join(Path.GetTempPath(), $"InsertSqlOutputBinding-{DateTime.Now.ToString("yyyy - dd - MM--HH - mm - ss")}.cs"); File.Copy(originalFile, testFile, true); AddSqlBindingParams parameters = new AddSqlBindingParams @@ -71,7 +71,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions Assert.True(result.Success); Assert.IsNull(result.ErrorMessage); - string expectedFileText = File.ReadAllText(Path.Join(testAzureFunctionsFolder, "AzureFunctionsOutputBinding.ts")); + string expectedFileText = File.ReadAllText(Path.Join(testAzureFunctionsFolder, "AzureFunctionsOutputBinding.cs")); string actualFileText = File.ReadAllText(testFile); Assert.AreEqual(expectedFileText, actualFileText); } @@ -83,8 +83,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions public void NoAzureFunctionForSqlBinding() { // copy the original file because the input binding will be inserted into the file - string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNoBindings.ts"); - string testFile = Path.Join(Path.GetTempPath(), string.Format("NoAzureFunctionForSqlBinding-{0}.ts", DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss"))); + string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNoBindings.cs"); + string testFile = Path.Join(Path.GetTempPath(), $"NoAzureFunctionForSqlBinding-{DateTime.Now.ToString("yyyy - dd - MM--HH - mm - ss")}.cs"); File.Copy(originalFile, testFile, true); AddSqlBindingParams parameters = new AddSqlBindingParams @@ -111,8 +111,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions public void MoreThanOneAzureFunctionWithSpecifiedName() { // copy the original file because the input binding will be inserted into the file - string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsMultipleSameFunction.ts"); - string testFile = Path.Join(Path.GetTempPath(), string.Format("MoreThanOneAzureFunctionWithSpecifiedName-{0}.ts", DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss"))); + string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsMultipleSameFunction.cs"); + string testFile = Path.Join(Path.GetTempPath(), $"MoreThanOneAzureFunctionWithSpecifiedName-{DateTime.Now.ToString("yyyy - dd - MM--HH - mm - ss")}.cs"); File.Copy(originalFile, testFile, true); AddSqlBindingParams parameters = new AddSqlBindingParams @@ -131,5 +131,52 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions Assert.NotNull(result.ErrorMessage); Assert.True(result.ErrorMessage.Equals(SR.MoreThanOneAzureFunctionWithName("GetArtists_get", testFile))); } + + /// + /// Verify getting the names of Azure functions in a file + /// + [Test] + public void GetAzureFunctions() + { + string testFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNoBindings.cs"); + + GetAzureFunctionsParams parameters = new GetAzureFunctionsParams + { + filePath = testFile + }; + + GetAzureFunctionsOperation operation = new GetAzureFunctionsOperation(parameters); + GetAzureFunctionsResult result = operation.GetAzureFunctions(); + + Assert.True(result.Success); + Assert.Null(result.ErrorMessage); + Assert.AreEqual(2, result.azureFunctions.Length); + Assert.AreEqual(result.azureFunctions[0], "GetArtists_get"); + Assert.AreEqual(result.azureFunctions[1], "NewArtist_post"); + } + + /// + /// Verify there are no errors when a file doesn't have any Azure functions + /// + [Test] + public void GetAzureFunctionsWhenNoFunctions() + { + // make blank file + string testFile = Path.Join(Path.GetTempPath(), $"NoAzureFunctions-{DateTime.Now.ToString("yyyy - dd - MM--HH - mm - ss")}.cs"); + FileStream fstream = File.Create(testFile); + fstream.Close(); + + GetAzureFunctionsParams parameters = new GetAzureFunctionsParams + { + filePath = testFile + }; + + GetAzureFunctionsOperation operation = new GetAzureFunctionsOperation(parameters); + GetAzureFunctionsResult result = operation.GetAzureFunctions(); + + Assert.True(result.Success); + Assert.Null(result.ErrorMessage); + Assert.AreEqual(0, result.azureFunctions.Length); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj index fcc92f32..88b67acf 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj @@ -38,6 +38,12 @@ PreserveNewest + + + + + +