diff --git a/src/libraries/Microsoft.PowerFx.Json/FormulaValueJSON.cs b/src/libraries/Microsoft.PowerFx.Json/FormulaValueJSON.cs index 845fda242c..46cf204162 100644 --- a/src/libraries/Microsoft.PowerFx.Json/FormulaValueJSON.cs +++ b/src/libraries/Microsoft.PowerFx.Json/FormulaValueJSON.cs @@ -6,7 +6,6 @@ using System.Diagnostics.Contracts; using System.Linq; using System.Text.Json; -using Microsoft.PowerFx.Core.Functions; using Microsoft.PowerFx.Core.IR; using Microsoft.PowerFx.Core.Types; using Microsoft.PowerFx.Functions; @@ -32,10 +31,22 @@ public static FormulaValue FromJson(string jsonString, FormulaType formulaType = public static FormulaValue FromJson(string jsonString, FormulaValueJsonSerializerSettings settings, FormulaType formulaType = null) { - using JsonDocument document = JsonDocument.Parse(jsonString); - JsonElement propBag = document.RootElement; - - return FromJson(propBag, settings, formulaType); + try + { + using JsonDocument document = JsonDocument.Parse(jsonString); + JsonElement propBag = document.RootElement; + + return FromJson(propBag, settings, formulaType); + } + catch (JsonException je) + { + return new ErrorValue(IRContext.NotInSource(formulaType), new ExpressionError() + { + Message = $"{je.GetType().Name} {je.Message}", + Span = new Syntax.Span(0, 0), + Kind = ErrorKind.Network + }); + } } /// diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/IntellisenseTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests/IntellisenseTests.cs index 4bbfd8aa46..b606a8d639 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests/IntellisenseTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests/IntellisenseTests.cs @@ -212,6 +212,39 @@ public void ConnectorIntellisenseTest_ServerError() Assert.Equal(string.Empty, list); } + [Fact] + public void ConnectorIntellisenseTest_EmptyResponse() + { + string expression = @"SQL.ExecuteProcedureV2(""default"", ""default"", ""sp_2"", { "; + + using LoggingTestServer testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output); + OpenApiDocument apiDoc = testConnector._apiDocument; + PowerFxConfig config = new PowerFxConfig(Features.PowerFxV1); + + using HttpClient httpClient = new HttpClient(testConnector); + using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("tip1-shared-002.azure-apim.net", "a2df3fb8-e4a4-e5e6-905c-e3dff9f93b46", "5f57ec83acef477b8ccc769e52fa22cc", () => "eyJ0eXA...", httpClient) + { + SessionId = "8e67ebdc-d402-455a-b33a-304820832383" + }; + + config.AddActionConnector("SQL", apiDoc, new ConsoleLogger(_output)); + + // The response is empty, this is to ensure we manage properly the exception coming from ExtractFromJson + testConnector.SetResponseFromFile(@"Responses\EmptyResponse.json"); + + RecalcEngine engine = new RecalcEngine(config); + BasicServiceProvider serviceProvider = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); + + CheckResult checkResult = engine.Check(expression, symbolTable: null); + + // This call should not throw an exception + IIntellisenseResult suggestions = engine.Suggest(checkResult, expression.Length, serviceProvider); + + // We don't get any result as the response is invalid + string list = string.Join("|", suggestions.Suggestions.Select(s => s.DisplayText.Text).OrderBy(x => x)); + Assert.Equal(string.Empty, list); + } + [Theory] [InlineData(1, 1, @"SQL.ExecuteProcedureV2(""default"", ""default"", ""sp_1"", ", @"p1")] // stored proc with 1 param, out of record public void ConnectorIntellisenseTestLSP(int responseIndex, int networkCall, string expression, string expectedSuggestions) diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/PowerPlatformConnectorTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests/PowerPlatformConnectorTests.cs index 3449226f42..3233c09b07 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests/PowerPlatformConnectorTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests/PowerPlatformConnectorTests.cs @@ -7,6 +7,7 @@ using System.Net; using System.Net.Http; using System.Text.Json; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.OpenApi.Models; @@ -1422,6 +1423,52 @@ public async Task SQL_ExecuteStoredProc_WithUserAgent() Assert.Equal(expected, actual); } + [Fact] + public async Task SQL_ExecuteStoredProc_WithEmptyServerResponse() + { + using var testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output); + var apiDoc = testConnector._apiDocument; + var config = new PowerFxConfig(Features.PowerFxV1); + using var httpClient = new HttpClient(testConnector); + using var client = new PowerPlatformConnectorClient("tip1-shared-002.azure-apim.net", "a2df3fb8-e4a4-e5e6-905c-e3dff9f93b46", "5f57ec83acef477b8ccc769e52fa22cc", () => "eyJ0eX...", "MyProduct/v1.2", httpClient) + { + SessionId = "8e67ebdc-d402-455a-b33a-304820832383" + }; + + config.AddActionConnector("SQL", apiDoc, new ConsoleLogger(_output)); + var engine = new RecalcEngine(config); + RuntimeConfig rc = new RuntimeConfig().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); + + testConnector.SetResponseFromFile(@"Responses\EmptyResponse.json"); + FormulaValue result = await engine.EvalAsync(@"SQL.ExecuteProcedureV2(""pfxdev-sql.database.windows.net"", ""connectortest"", ""sp_1"", { p1: 50 })", CancellationToken.None, new ParserOptions() { AllowsSideEffects = true }, runtimeConfig: rc).ConfigureAwait(false); + Assert.True(result is BlankValue); + } + + [Fact] + public async Task SQL_ExecuteStoredProc_WithInvalidResponse() + { + using var testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output); + var apiDoc = testConnector._apiDocument; + var config = new PowerFxConfig(Features.PowerFxV1); + using var httpClient = new HttpClient(testConnector); + using var client = new PowerPlatformConnectorClient("tip1-shared-002.azure-apim.net", "a2df3fb8-e4a4-e5e6-905c-e3dff9f93b46", "5f57ec83acef477b8ccc769e52fa22cc", () => "eyJ0eX...", "MyProduct/v1.2", httpClient) + { + SessionId = "8e67ebdc-d402-455a-b33a-304820832383" + }; + + config.AddActionConnector("SQL", apiDoc, new ConsoleLogger(_output)); + var engine = new RecalcEngine(config); + RuntimeConfig rc = new RuntimeConfig().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); + + testConnector.SetResponseFromFile(@"Responses\Invalid.txt"); + FormulaValue result = await engine.EvalAsync(@"SQL.ExecuteProcedureV2(""pfxdev-sql.database.windows.net"", ""connectortest"", ""sp_1"", { p1: 50 })", CancellationToken.None, new ParserOptions() { AllowsSideEffects = true }, runtimeConfig: rc).ConfigureAwait(false); + + ErrorValue ev = Assert.IsType(result); + string message = ev.Errors[0].Message; + + Assert.Equal(@$"SQL.ExecuteProcedureV2 failed: JsonReaderException '+' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 0.", message); + } + [Fact] public async Task SharePointOnlineTest() { diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EmptyResponse.json b/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EmptyResponse.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Invalid.txt b/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Invalid.txt new file mode 100644 index 0000000000..9b26e9b102 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Invalid.txt @@ -0,0 +1 @@ ++ \ No newline at end of file