From f0af967deb8a79e719dd29a835d97563f185b5d2 Mon Sep 17 00:00:00 2001 From: Luc Genetier <69138830+LucGenetier@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:45:38 +0100 Subject: [PATCH] Add ReturnEnumsAsPrimitive connector settings (#2807) When SupportXMsEnumValues is set to true, we want to force 'modelAsString' to false via ReturnEnumsAsPrimitive flag This will keep the x-ms-enum-* extensions to be parsed while creating a consistent FormulaType as string or decimal --- .../OpenApiExtensions.cs | 10 ++ .../Public/ConnectorSettings.cs | 11 +- .../Public/ConnectorType.cs | 4 +- .../PowerPlatformConnectorTests.cs | 136 ++++++++++++------ 4 files changed, 116 insertions(+), 45 deletions(-) diff --git a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs index 0f6fc9fe06..3a8656c951 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs @@ -716,6 +716,11 @@ private static ConnectorType TryGetOptionSet(ISwaggerParameter openApiParameter, return new ConnectorType(schema, openApiParameter, FormulaType.String, list: list, isNumber: isNumber); } + if (settings.Settings.ReturnEnumsAsPrimitive) + { + return new ConnectorType(schema, openApiParameter, isNumber ? FormulaType.Decimal : FormulaType.String, list: list, isNumber: isNumber); + } + return new ConnectorType(schema, openApiParameter, optionSet.FormulaType); } @@ -737,6 +742,11 @@ private static ConnectorType TryGetOptionSet(ISwaggerParameter openApiParameter, return new ConnectorType(schema, openApiParameter, FormulaType.String, list: dic); } + if (settings.Settings.ReturnEnumsAsPrimitive) + { + return new ConnectorType(schema, openApiParameter, isNumber ? FormulaType.Decimal : FormulaType.String, list: list, isNumber: isNumber); + } + return new ConnectorType(schema, openApiParameter, optionSet.FormulaType); } else diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSettings.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSettings.cs index 9605730c4b..c40b808bc0 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSettings.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSettings.cs @@ -14,7 +14,8 @@ public class ConnectorSettings internal static readonly ConnectorSettings DefaultCdp = new ConnectorSettings(null) { Compatibility = ConnectorCompatibility.CdpCompatibility, - SupportXMsEnumValues = true + SupportXMsEnumValues = true, + ReturnEnumsAsPrimitive = false }; public ConnectorSettings(string @namespace) @@ -85,7 +86,13 @@ public bool ExposeInternalParamsWithoutDefaultValue /// By default action connectors won't parse x-ms-enum-values. /// Only CDP connectors will have this enabled by default. /// - public bool SupportXMsEnumValues { get; init; } = false; + public bool SupportXMsEnumValues { get; init; } = false; + + /// + /// This flag will force all enums to be returns as FormulaType.String or FormulaType.Decimal regardless of x-ms-enum-*. + /// This flag is only in effect when SupportXMsEnumValues is true. + /// + public bool ReturnEnumsAsPrimitive { get; init; } = false; public ConnectorCompatibility Compatibility { get; init; } = ConnectorCompatibility.Default; } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs index 3ac353475e..83e8204eb3 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs @@ -162,8 +162,8 @@ internal ConnectorType(ISwaggerSchema schema, ISwaggerParameter openApiParameter { if (list != null && list.Any()) { - EnumValues = list.Select, FormulaValue>(kvp => isNumber ? FormulaValue.New(decimal.Parse(kvp.Value.Value, CultureInfo.InvariantCulture)) : FormulaValue.New(kvp.Value)).ToArray(); - EnumDisplayNames = list.Select(list => list.Key.Value).ToArray(); + EnumValues = list.Select, FormulaValue>(kvp => isNumber ? FormulaValue.New(decimal.Parse(kvp.Key.Value, CultureInfo.InvariantCulture)) : FormulaValue.New(kvp.Key)).ToArray(); + EnumDisplayNames = list.Select(list => list.Value.Value).ToArray(); } else { diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorTests.cs index ce52cbe1cf..f5817a9ea9 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorTests.cs @@ -46,16 +46,30 @@ private static void AssertEqual(string expected, string actual) } [Theory] - [InlineData(ConnectorCompatibility.SwaggerCompatibility, true)] - [InlineData(ConnectorCompatibility.SwaggerCompatibility, false)] - [InlineData(ConnectorCompatibility.PowerAppsCompatibility, true)] - [InlineData(ConnectorCompatibility.PowerAppsCompatibility, false)] - [InlineData(ConnectorCompatibility.CdpCompatibility, true)] - [InlineData(ConnectorCompatibility.CdpCompatibility, false)] - public void MSNWeather_OptionSets(ConnectorCompatibility connectorCompatibility, bool supportXMsEnumValues) + [InlineData(ConnectorCompatibility.SwaggerCompatibility, true, false)] + [InlineData(ConnectorCompatibility.SwaggerCompatibility, false, false)] + [InlineData(ConnectorCompatibility.SwaggerCompatibility, true, true)] + [InlineData(ConnectorCompatibility.SwaggerCompatibility, false, true)] + [InlineData(ConnectorCompatibility.PowerAppsCompatibility, true, false)] + [InlineData(ConnectorCompatibility.PowerAppsCompatibility, false, false)] + [InlineData(ConnectorCompatibility.PowerAppsCompatibility, true, true)] + [InlineData(ConnectorCompatibility.PowerAppsCompatibility, false, true)] + [InlineData(ConnectorCompatibility.CdpCompatibility, true, false)] + [InlineData(ConnectorCompatibility.CdpCompatibility, false, false)] + [InlineData(ConnectorCompatibility.CdpCompatibility, true, true)] + [InlineData(ConnectorCompatibility.CdpCompatibility, false, true)] + public void MSNWeather_OptionSets(ConnectorCompatibility connectorCompatibility, bool supportXMsEnumValues, bool returnEnumsAsPrimitive) { using var testConnector = new LoggingTestServer(@"Swagger\MSNWeather.json", _output); - List functions = OpenApiParser.GetFunctions(new ConnectorSettings("MSNWeather") { Compatibility = connectorCompatibility, SupportXMsEnumValues = supportXMsEnumValues }, testConnector._apiDocument).ToList(); + List functions = OpenApiParser.GetFunctions( + new ConnectorSettings("MSNWeather") + { + Compatibility = connectorCompatibility, + SupportXMsEnumValues = supportXMsEnumValues, + ReturnEnumsAsPrimitive = returnEnumsAsPrimitive + }, + testConnector._apiDocument).ToList(); + ConnectorFunction currentWeather = functions.First(f => f.Name == "CurrentWeather"); Assert.Equal(2, currentWeather.RequiredParameters.Length); @@ -69,35 +83,56 @@ public void MSNWeather_OptionSets(ConnectorCompatibility connectorCompatibility, if (connectorCompatibility == ConnectorCompatibility.CdpCompatibility || supportXMsEnumValues) { - Assert.Equal(FormulaType.OptionSetValue, currentWeather.RequiredParameters[1].FormulaType); + if (returnEnumsAsPrimitive) + { + Assert.Equal(FormulaType.String, currentWeather.RequiredParameters[1].FormulaType); + } + else + { + Assert.Equal(FormulaType.OptionSetValue, currentWeather.RequiredParameters[1].FormulaType); + } // Dictionary is used here, so we need to reorder Assert.Equal("Imperial,Metric", string.Join(",", currentWeather.RequiredParameters[1].ConnectorType.Enum.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Key))); Assert.Equal("I,C", string.Join(",", currentWeather.RequiredParameters[1].ConnectorType.Enum.OrderBy(kvp => kvp.Key).Select(kvp => (kvp.Value as StringValue).Value))); - Assert.Equal("Imperial,Metric", string.Join(",", currentWeather.RequiredParameters[1].ConnectorType.EnumDisplayNames)); + Assert.Equal("Imperial,Metric", string.Join(",", currentWeather.RequiredParameters[1].ConnectorType.EnumDisplayNames)); } else { - Assert.Equal(FormulaType.String, currentWeather.RequiredParameters[1].FormulaType); + Assert.Equal(FormulaType.String, currentWeather.RequiredParameters[1].FormulaType); } Assert.Equal("I,C", string.Join(",", currentWeather.RequiredParameters[1].ConnectorType.EnumValues.Select(fv => (fv as StringValue).Value))); } [Theory] - [InlineData(ConnectorCompatibility.SwaggerCompatibility, true)] - [InlineData(ConnectorCompatibility.SwaggerCompatibility, false)] - [InlineData(ConnectorCompatibility.PowerAppsCompatibility, true)] - [InlineData(ConnectorCompatibility.PowerAppsCompatibility, false)] - [InlineData(ConnectorCompatibility.CdpCompatibility, true)] - [InlineData(ConnectorCompatibility.CdpCompatibility, false)] + [InlineData(ConnectorCompatibility.SwaggerCompatibility, true, false)] + [InlineData(ConnectorCompatibility.SwaggerCompatibility, false, false)] + [InlineData(ConnectorCompatibility.SwaggerCompatibility, true, true)] + [InlineData(ConnectorCompatibility.SwaggerCompatibility, false, true)] + [InlineData(ConnectorCompatibility.PowerAppsCompatibility, true, false)] + [InlineData(ConnectorCompatibility.PowerAppsCompatibility, false, false)] + [InlineData(ConnectorCompatibility.PowerAppsCompatibility, true, true)] + [InlineData(ConnectorCompatibility.PowerAppsCompatibility, false, true)] + [InlineData(ConnectorCompatibility.CdpCompatibility, true, false)] + [InlineData(ConnectorCompatibility.CdpCompatibility, false, false)] + [InlineData(ConnectorCompatibility.CdpCompatibility, true, true)] + [InlineData(ConnectorCompatibility.CdpCompatibility, false, true)] // Option set with numeric logical names - public void DimeScheduler_OptionSets(ConnectorCompatibility connectorCompatibility, bool supportXMsEnumValues) + public void DimeScheduler_OptionSets(ConnectorCompatibility connectorCompatibility, bool supportXMsEnumValues, bool returnEnumsAsPrimitive) { using var testConnector = new LoggingTestServer(@"Swagger\Dime.Scheduler.json", _output); - List functions = OpenApiParser.GetFunctions(new ConnectorSettings("DimeScheduler") { Compatibility = connectorCompatibility, SupportXMsEnumValues = supportXMsEnumValues }, testConnector._apiDocument).ToList(); + List functions = OpenApiParser.GetFunctions( + new ConnectorSettings("DimeScheduler") + { + Compatibility = connectorCompatibility, + SupportXMsEnumValues = supportXMsEnumValues, + ReturnEnumsAsPrimitive = returnEnumsAsPrimitive + }, + testConnector._apiDocument).ToList(); + ConnectorFunction actionUriUpsert = functions.First(f => f.Name == "actionUriUpsert"); Assert.Equal(5, actionUriUpsert.OptionalParameters.Length); @@ -106,35 +141,54 @@ public void DimeScheduler_OptionSets(ConnectorCompatibility connectorCompatibili if (connectorCompatibility == ConnectorCompatibility.CdpCompatibility || supportXMsEnumValues) { - Assert.Equal(FormulaType.OptionSetValue, actionUriUpsert.OptionalParameters[2].FormulaType); + if (returnEnumsAsPrimitive) + { + Assert.Equal(FormulaType.Decimal, actionUriUpsert.OptionalParameters[2].FormulaType); + } + else + { + Assert.Equal(FormulaType.OptionSetValue, actionUriUpsert.OptionalParameters[2].FormulaType); + } // Dictionary is used here, so we need to reorder Assert.Equal("Planning Board,Appointment,Task,Map", string.Join(",", actionUriUpsert.OptionalParameters[2].ConnectorType.Enum.Select(kvp => kvp.Key))); Assert.Equal("0,1,2,3", string.Join(",", actionUriUpsert.OptionalParameters[2].ConnectorType.Enum.Select(kvp => (kvp.Value as DecimalValue).Value))); - Assert.Equal("Planning Board,Appointment,Task,Map", string.Join(",", actionUriUpsert.OptionalParameters[2].ConnectorType.EnumDisplayNames)); + Assert.Equal("Planning Board,Appointment,Task,Map", string.Join(",", actionUriUpsert.OptionalParameters[2].ConnectorType.EnumDisplayNames)); } else { - Assert.Equal(FormulaType.Decimal, actionUriUpsert.OptionalParameters[2].FormulaType); + Assert.Equal(FormulaType.Decimal, actionUriUpsert.OptionalParameters[2].FormulaType); } Assert.Equal("0,1,2,3", string.Join(",", actionUriUpsert.OptionalParameters[2].ConnectorType.EnumValues.Select(fv => (fv as DecimalValue).Value))); } [Theory] - [InlineData(ConnectorCompatibility.SwaggerCompatibility, true)] - [InlineData(ConnectorCompatibility.SwaggerCompatibility, false)] - [InlineData(ConnectorCompatibility.PowerAppsCompatibility, true)] - [InlineData(ConnectorCompatibility.PowerAppsCompatibility, false)] - [InlineData(ConnectorCompatibility.CdpCompatibility, true)] - [InlineData(ConnectorCompatibility.CdpCompatibility, false)] - - // Option set with numeric logical names - public void ACSL_OptionSets(ConnectorCompatibility connectorCompatibility, bool supportXMsEnumValues) + [InlineData(ConnectorCompatibility.SwaggerCompatibility, true, false)] + [InlineData(ConnectorCompatibility.SwaggerCompatibility, false, false)] + [InlineData(ConnectorCompatibility.SwaggerCompatibility, true, true)] + [InlineData(ConnectorCompatibility.SwaggerCompatibility, false, true)] + [InlineData(ConnectorCompatibility.PowerAppsCompatibility, true, false)] + [InlineData(ConnectorCompatibility.PowerAppsCompatibility, false, false)] + [InlineData(ConnectorCompatibility.PowerAppsCompatibility, true, true)] + [InlineData(ConnectorCompatibility.PowerAppsCompatibility, false, true)] + [InlineData(ConnectorCompatibility.CdpCompatibility, true, false)] + [InlineData(ConnectorCompatibility.CdpCompatibility, false, false)] + [InlineData(ConnectorCompatibility.CdpCompatibility, true, true)] + [InlineData(ConnectorCompatibility.CdpCompatibility, false, true)] + public void ACSL_OptionSets(ConnectorCompatibility connectorCompatibility, bool supportXMsEnumValues, bool returnEnumsAsPrimitive) { using var testConnector = new LoggingTestServer(@"Swagger\Azure Cognitive Service for Language v2.2.json", _output); - List functions = OpenApiParser.GetFunctions(new ConnectorSettings("ACSL") { Compatibility = connectorCompatibility, SupportXMsEnumValues = supportXMsEnumValues }, testConnector._apiDocument).ToList(); + List functions = OpenApiParser.GetFunctions( + new ConnectorSettings("ACSL") + { + Compatibility = connectorCompatibility, + SupportXMsEnumValues = supportXMsEnumValues, + ReturnEnumsAsPrimitive = returnEnumsAsPrimitive + }, + testConnector._apiDocument).ToList(); + ConnectorFunction analyzeConversationTranscriptSubmitJob = functions.Single(f => f.Name == "AnalyzeConversationTranscriptSubmitJob"); // AnalyzeConversationTranscript_SubmitJob defined at line 1226 of swagger file @@ -143,19 +197,19 @@ public void ACSL_OptionSets(ConnectorCompatibility connectorCompatibility, bool // conversations parameter at line 2577, defined at line 2580(TranscriptConversation) // conversationItems parameter at line 2585, defined at line 2624(TranscriptConversationItem) // role parameter at line 2641 is having extension x-ms-enum with modelAsString set to true - ConnectorType connectorType = analyzeConversationTranscriptSubmitJob.RequiredParameters[0].ConnectorType; + ConnectorType connectorType = analyzeConversationTranscriptSubmitJob.RequiredParameters[0].ConnectorType; ConnectorType role = connectorType.Fields[0].Fields[0].Fields[0].Fields[connectorCompatibility == ConnectorCompatibility.Default ? 4 : 3]; Assert.Equal("role", role.Name); // Type is always a string here as to be an optionset, we need (ConnectorCompatibility = CdpCompatibility or SupportXMsEnumValues = true) AND (modelAsString = false) Assert.Equal(FormulaType.String, role.FormulaType); Assert.True(role.IsEnum); - + Assert.Equal( connectorCompatibility != ConnectorCompatibility.Default ? "![conversations:![conversationItems:*[audioTimings:*[duration:w, offset:w, word:s], id:s, itn:s, language:s, lexical:s, maskedItn:s, participantId:s, role:s, text:s], domain:s, language:s]]" - : "![conversations:![conversationItems:*[audioTimings:*[duration:w, offset:w, word:s], id:s, itn:s, language:s, lexical:s, maskedItn:s, modality:s, participantId:s, role:s, text:s], domain:s, language:s]]", - connectorType.FormulaType._type.ToString()); + : "![conversations:![conversationItems:*[audioTimings:*[duration:w, offset:w, word:s], id:s, itn:s, language:s, lexical:s, maskedItn:s, modality:s, participantId:s, role:s, text:s], domain:s, language:s]]", + connectorType.FormulaType._type.ToString()); if (connectorCompatibility == ConnectorCompatibility.CdpCompatibility || supportXMsEnumValues) { @@ -1969,16 +2023,16 @@ public async Task SendEmail() public async Task AiSensitivityTest() { using LoggingTestServer testConnector = new LoggingTestServer(@"Swagger\SendMail.json", _output); - OpenApiDocument apiDoc = testConnector._apiDocument; - + OpenApiDocument apiDoc = testConnector._apiDocument; + ConnectorSettings connectorSettings = new ConnectorSettings("exob") - { + { Compatibility = ConnectorCompatibility.SwaggerCompatibility, AllowUnsupportedFunctions = true, IncludeInternalFunctions = true, - ReturnUnknownRecordFieldsAsUntypedObjects = true + ReturnUnknownRecordFieldsAsUntypedObjects = true }; - + List functions = OpenApiParser.GetFunctions(connectorSettings, apiDoc).OrderBy(f => f.Name).ToList(); ConnectorFunction sendmail = functions.First(f => f.Name == "SendEmailV3");