diff --git a/.gitignore b/.gitignore index b64c37cd2..872d1100b 100644 --- a/.gitignore +++ b/.gitignore @@ -355,14 +355,12 @@ Output/ # Tool folder tool/ +# Report folder +report/ + # ApiCompat suppression files suppress.*.xml -# Connector analysis reports -/report/Analysis.txt -/report/Report.json -/report/Report_*.json - # global.json /src/global.json /global.json diff --git a/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs b/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs index 18dca4a2f..b273da4a6 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs @@ -827,6 +827,11 @@ namespace Microsoft.PowerFx.Connectors // Only used by ConnectorTexlFunction private DType[] GetParamTypes() { + if (RequiredParameters == null) + { + return Array.Empty(); + } + IEnumerable parameterTypes = RequiredParameters.Select(parameter => parameter.FormulaType._type); if (OptionalParameters.Any()) { diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Execution/HttpFunctionInvoker.cs b/src/libraries/Microsoft.PowerFx.Connectors/Execution/HttpFunctionInvoker.cs index 88493c921..2d752f399 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Execution/HttpFunctionInvoker.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Execution/HttpFunctionInvoker.cs @@ -67,7 +67,7 @@ namespace Microsoft.PowerFx.Connectors } else if (param.Schema.Default != null) { - if (OpenApiExtensions.TryGetOpenApiValue(param.Schema.Default, out FormulaValue defaultValue)) + if (OpenApiExtensions.TryGetOpenApiValue(param.Schema.Default, null, out FormulaValue defaultValue)) { bodyParts.Add(param.Name, (param.Schema, defaultValue)); } @@ -239,9 +239,19 @@ namespace Microsoft.PowerFx.Connectors lst.Remove(field1); lst.Add(field2); } + else if (field1.Value is BlankValue) + { + lst.Remove(field1); + lst.Add(field2); + } + else if (field2.Value is BlankValue) + { + lst.Remove(field2); + lst.Add(field1); + } else { - throw new ArgumentException($"Cannot merge {field1.Name} of type {field1.Value.GetType().Name} with {field2.Name} of type {field2.Value.GetType().Name}"); + throw new ArgumentException($"Cannot merge '{field1.Name}' of type {field1.Value.GetType().Name} with '{field2.Name}' of type {field2.Value.GetType().Name}"); } } } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs index fdc6bae73..e4c8d6e39 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs @@ -123,47 +123,104 @@ namespace Microsoft.PowerFx.Connectors } public static bool TryGetDefaultValue(this OpenApiSchema schema, FormulaType formulaType, out FormulaValue defaultValue) - { - IOpenApiAny openApiDefaultValue = schema.Default; - - if (openApiDefaultValue == null) - { - if (formulaType is RecordType rt && schema.Properties != null) - { - List values = new List(); - - foreach (NamedFormulaType nft in rt.GetFieldTypes()) - { - string columnName = nft.Name.Value; - - if (schema.Properties.ContainsKey(columnName)) - { - if (schema.Properties[columnName].TryGetDefaultValue(nft.Type, out FormulaValue innerDefaultValue)) - { - values.Add(new NamedValue(columnName, innerDefaultValue)); - } - } - } - - if (values.Any()) - { - defaultValue = new InMemoryRecordValue(IRContext.NotInSource(rt), values); - return true; - } - } - - defaultValue = null; - return false; + { + if (schema.Type == "array" && formulaType is TableType tableType && schema.Items != null) + { + RecordType recordType = tableType.ToRecord(); + bool b = schema.Items.TryGetDefaultValue(recordType, out FormulaValue itemDefaultValue); + + if (!b || itemDefaultValue is BlankValue) + { + defaultValue = FormulaValue.NewTable(recordType); + return true; + } + + if (itemDefaultValue is RecordValue itemDefaultRecordValue) + { + defaultValue = FormulaValue.NewTable(recordType, itemDefaultRecordValue); + return true; + } + + defaultValue = FormulaValue.NewTable(recordType, FormulaValue.NewRecordFromFields(new NamedValue[] { new NamedValue(TableValue.ValueName, itemDefaultValue) })); + return true; } - return TryGetOpenApiValue(openApiDefaultValue, out defaultValue); + if (schema.Type == "object" || schema.Default == null) + { + if (formulaType is RecordType recordType2 && schema.Properties != null) + { + List values = new List(); + + foreach (NamedFormulaType namedFormulaType in recordType2.GetFieldTypes()) + { + string columnName = namedFormulaType.Name.Value; + + if (schema.Properties.ContainsKey(columnName)) + { + if (schema.Properties[columnName].TryGetDefaultValue(namedFormulaType.Type, out FormulaValue innerDefaultValue)) + { + values.Add(new NamedValue(columnName, innerDefaultValue)); + } + else + { + values.Add(new NamedValue(columnName, FormulaValue.NewBlank(namedFormulaType.Type))); + } + } + } + + defaultValue = values.Any(v => v.Value is not BlankValue) ? FormulaValue.NewRecordFromFields(values) : FormulaValue.NewBlank(recordType2); + return true; + } + + if (schema.Default == null) + { + defaultValue = null; + return false; + } + } + + return TryGetOpenApiValue(schema.Default, formulaType, out defaultValue); } - internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, out FormulaValue formulaValue) + internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, FormulaType formulaType, out FormulaValue formulaValue) { if (openApiAny is OpenApiString str) - { - formulaValue = FormulaValue.New(str.Value); + { + formulaValue = null; + + if (formulaType != null && formulaType is not StringType && formulaType is not RecordType) + { + if (formulaType is BooleanType && bool.TryParse(str.Value, out bool b)) + { + formulaValue = FormulaValue.New(b); + } + else if (formulaType is DecimalType && decimal.TryParse(str.Value, out decimal d)) + { + formulaValue = FormulaValue.New(d); + } + else if (formulaType is DateTimeType) + { + if (DateTime.TryParse(str.Value, out DateTime dt)) + { + formulaValue = FormulaValue.New(dt); + } + + throw new PowerFxConnectorException($"Unsupported DateTime format: {str.Value}"); + } + else if (formulaType is NumberType && double.TryParse(str.Value, out double dbl)) + { + formulaValue = FormulaValue.New(dbl); + } + else if (formulaType is OptionSetValueType osvt && osvt.TryGetValue(new DName(str.Value), out OptionSetValue osv)) + { + formulaValue = osv; + } + } + + if (formulaValue == null) + { + formulaValue = FormulaValue.New(str.Value); + } } else if (openApiAny is OpenApiInteger intVal) { @@ -210,7 +267,17 @@ namespace Microsoft.PowerFx.Connectors foreach (IOpenApiAny element in arr) { - bool ba = TryGetOpenApiValue(element, out FormulaValue fv); + FormulaType newType = null; + + if (formulaType != null) + { + if (formulaType is TableType tableType) + { + newType = tableType.ToRecord(); + } + } + + bool ba = TryGetOpenApiValue(element, newType, out FormulaValue fv); if (!ba) { formulaValue = null; @@ -232,7 +299,7 @@ namespace Microsoft.PowerFx.Connectors foreach (KeyValuePair kvp in o) { - if (TryGetOpenApiValue(kvp.Value, out FormulaValue fv)) + if (TryGetOpenApiValue(kvp.Value, null, out FormulaValue fv)) { dvParams[kvp.Key] = fv; } @@ -390,7 +457,7 @@ namespace Microsoft.PowerFx.Connectors return new ConnectorType(schema, openApiParameter, FormulaType.UntypedObject); } - chain.Push(innerA); + chain.Push(innerA); ConnectorType arrayType = new OpenApiParameter() { Name = "Array", Required = true, Schema = schema.Items, Extensions = schema.Items.Extensions }.ToConnectorType(chain, level + 1); chain.Pop(); @@ -536,13 +603,13 @@ namespace Microsoft.PowerFx.Connectors } if (response == null) - { - // No return type. Void() method. + { + // No return type. Void() method. return (new ConnectorType(), null); } if (response.Content.Count == 0) - { + { OpenApiSchema schema = new OpenApiSchema() { Type = "string", Format = "binary" }; ConnectorType connectorType = new OpenApiParameter() { Name = "response", Required = true, Schema = schema, Extensions = response.Extensions }.ToConnectorType(); return (connectorType, null); @@ -727,7 +794,7 @@ namespace Microsoft.PowerFx.Connectors foreach (KeyValuePair prm in opPrms) { - if (!OpenApiExtensions.TryGetOpenApiValue(prm.Value, out FormulaValue fv)) + if (!OpenApiExtensions.TryGetOpenApiValue(prm.Value, null, out FormulaValue fv)) { throw new NotImplementedException($"Unsupported param with OpenApi type {prm.Value.GetType().FullName}, key = {prm.Key}"); } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiParser.cs b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiParser.cs index 9a4ace143..e3dc70b41 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiParser.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiParser.cs @@ -383,6 +383,7 @@ namespace Microsoft.PowerFx.Connectors isSupported = false; notSupportedReason = $"OpenApiParameter is null"; logger?.LogWarning($"OperationId {op.OperationId} has a null OpenApiParameter"); + return; } if (param.Deprecated) diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSchema.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSchema.cs index 07a9d1851..b35f7c498 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSchema.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSchema.cs @@ -24,7 +24,7 @@ namespace Microsoft.PowerFx.Connectors public string Title => Schema.Title; public FormulaType FormulaType => UseHiddenTypes ? ConnectorType.HiddenRecordType : ConnectorType.FormulaType; - + internal RecordType HiddenRecordType => ConnectorType.HiddenRecordType; public string Summary => ConnectorExtensions.Summary; @@ -36,8 +36,7 @@ namespace Microsoft.PowerFx.Connectors Schema = openApiParameter.Schema; UseHiddenTypes = useHiddenTypes; ConnectorType = openApiParameter.ToConnectorType(); - DefaultValue = openApiParameter.Schema.TryGetDefaultValue(FormulaType, out FormulaValue defaultValue) ? defaultValue : null; - + DefaultValue = openApiParameter.Schema.TryGetDefaultValue(FormulaType, out FormulaValue defaultValue) && defaultValue is not BlankValue ? defaultValue : null; ConnectorExtensions = new ConnectorExtensions(openApiParameter, bodyExtensions); } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs index 7a3d4f25a..5439184fe 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs @@ -96,7 +96,7 @@ namespace Microsoft.PowerFx.Connectors if (IsEnum) { - EnumValues = schema.Enum.Select(oaa => OpenApiExtensions.TryGetOpenApiValue(oaa, out FormulaValue fv) ? fv : throw new NotSupportedException($"Invalid conversion for type {oaa.GetType().Name} in enum")).ToArray(); + EnumValues = schema.Enum.Select(oaa => OpenApiExtensions.TryGetOpenApiValue(oaa, null, out FormulaValue fv) ? fv : throw new NotSupportedException($"Invalid conversion for type {oaa.GetType().Name} in enum")).ToArray(); EnumDisplayNames = schema.Extensions != null && schema.Extensions.TryGetValue(XMsEnumDisplayName, out IOpenApiExtension enumNames) && enumNames is OpenApiArray oaa ? oaa.Cast().Select(oas => oas.Value).ToArray() : Array.Empty(); diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/InternalTesting.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests/InternalTesting.cs index f5d0f0800..0e1c5e7d0 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests/InternalTesting.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests/InternalTesting.cs @@ -4,15 +4,17 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Dynamic; using System.IO; using System.Linq; using System.Text.Json; using System.Text.RegularExpressions; using Microsoft.OpenApi.Models; using Microsoft.PowerFx.Tests; -using Microsoft.VisualStudio.TestPlatform.Utilities; +using Microsoft.PowerFx.Types; using Xunit; using Xunit.Abstractions; +using YamlDotNet.Serialization; namespace Microsoft.PowerFx.Connectors.Tests { @@ -35,12 +37,19 @@ namespace Microsoft.PowerFx.Connectors.Tests { string outFolder = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, @"..\..\..\..\..\..")); string srcFolder = Path.GetFullPath(Path.Combine(outFolder, "..")); - string reportName = @"report\Analysis.txt"; - string jsonReport = @"report\Report.json"; + string reportFolder = @"report"; + string reportName = @$"{reportFolder}\Analysis.txt"; + string jsonReport = @$"{reportFolder}\Report.json"; // New report name every second string jsonReport2 = @$"report\Report_{Math.Round(DateTime.UtcNow.Ticks / 1e7):00000000000}.json"; + string outFolderPath = Path.Combine(outFolder, reportFolder); + if (Directory.Exists(outFolderPath)) + { + Directory.Delete(outFolderPath, true); + } + // On build servers: ENV: C:\__w\1\s\pfx\src\tests\Microsoft.PowerFx.Connectors.Tests\bin\Release\netcoreapp3.1 // Locally : ENV: C:\Data\Power-Fx\src\tests\Microsoft.PowerFx.Connectors.Tests\bin\Debug\netcoreapp3.1 _output.WriteLine($"ENV: {Environment.CurrentDirectory}"); @@ -48,7 +57,7 @@ namespace Microsoft.PowerFx.Connectors.Tests _output.WriteLine($"SRC: {srcFolder}"); Directory.CreateDirectory(Path.Combine(outFolder, "report")); - GenerateReport(reportName, outFolder, srcFolder); + GenerateReport(reportFolder, reportName, outFolder, srcFolder); AnalyzeReport(reportName, outFolder, srcFolder, jsonReport); File.Copy(Path.Combine(outFolder, jsonReport), Path.Combine(outFolder, jsonReport2)); @@ -325,7 +334,7 @@ namespace Microsoft.PowerFx.Connectors.Tests White = 3, // #FFFFFF } - private void GenerateReport(string reportName, string outFolder, string srcFolder) + private void GenerateReport(string reportFolder, string reportName, string outFolder, string srcFolder) { int i = 0; int j = 0; @@ -359,28 +368,47 @@ namespace Microsoft.PowerFx.Connectors.Tests // Check we can add the service (more comprehensive test) config.AddActionConnector("Connector", doc); - - IEnumerable functions2 = OpenApiParser.GetFunctions(new ConnectorSettings("C1") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, doc); + + IEnumerable functions2 = OpenApiParser.GetFunctions(new ConnectorSettings("C1") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, doc); + string cFolder = Path.Combine(outFolder, reportFolder, doc.Info.Title); + + int ix = 2; + while (Directory.Exists(cFolder)) + { + cFolder = Path.Combine(outFolder, reportFolder, doc.Info.Title) + $"_{ix++}"; + } + + Directory.CreateDirectory(cFolder); foreach (ConnectorFunction cf1 in functions) { ConnectorFunction cf2 = functions2.First(f => f.Name == cf1.Name); - string rp1 = string.Join(", ", cf1.RequiredParameters.Select(rp => rp.Name)); - string rp2 = string.Join(", ", cf2.RequiredParameters.Select(rp => rp.Name)); - - if (rp1 != rp2) + if (cf1.RequiredParameters != null && cf2.RequiredParameters != null) { - string s = $"Function {cf1.Name} - Required parameters are different: [{rp1}] -- [{rp2}]"; - if (w2.ContainsKey(title)) + string rp1 = string.Join(", ", cf1.RequiredParameters.Select(rp => rp.Name)); + string rp2 = string.Join(", ", cf2.RequiredParameters.Select(rp => rp.Name)); + + if (rp1 != rp2) { - w2[title].Add(s); + string s = $"Function {cf1.Name} - Required parameters are different: [{rp1}] -- [{rp2}]"; + if (w2.ContainsKey(title)) + { + w2[title].Add(s); + } + else + { + w2.Add(title, new List() { s }); + } } - else - { - w2.Add(title, new List() { s }); - } } + + dynamic obj = cf1.ToExpando(swaggerFile); + var serializer = new SerializerBuilder().Build(); + string yaml = serializer.Serialize(obj); + + string functionFile = Path.Combine(cFolder, cf1.OriginalName.Replace("/", "_") + ".yaml"); + File.WriteAllText(functionFile, yaml); } } catch (Exception ex) @@ -400,7 +428,7 @@ namespace Microsoft.PowerFx.Connectors.Tests } } - using StreamWriter writer2 = new StreamWriter(Path.Combine(outFolder, "ConnectorComparison.txt"), append: false); + using StreamWriter writer2 = new StreamWriter(Path.Combine(outFolder, reportFolder, "ConnectorComparison.txt"), append: false); foreach (KeyValuePair> kvp in w2.OrderBy(kvp => kvp.Key)) { @@ -509,5 +537,306 @@ namespace Microsoft.PowerFx.Connectors.Tests console.WriteLine(obj.ToString()); } } + + public static ExpandoObject ToExpando(this ConnectorFunction connectorFunction, string swaggerFile) + { + dynamic func = new ExpandoObject(); + + func.Name = connectorFunction.Name; + func.OperationId = connectorFunction.OriginalName; + func.Method = connectorFunction.HttpMethod.ToString().ToUpperInvariant(); + func.Path = connectorFunction.OperationPath; + func.SwaggerFile = swaggerFile; + + if (!string.IsNullOrEmpty(connectorFunction.Description)) + { + func.Description = connectorFunction.Description; + } + + if (!string.IsNullOrEmpty(connectorFunction.Summary)) + { + func.Summary = connectorFunction.Summary; + } + + func.IsBehavior = connectorFunction.IsBehavior; + func.IsDeprecated = connectorFunction.IsDeprecated; + func.IsInternal = connectorFunction.IsInternal; + func.IsPageable = connectorFunction.IsPageable; + + if (connectorFunction.RequiresUserConfirmation) + { + func.RequiresUserConfirmation = connectorFunction.RequiresUserConfirmation; + } + + if (!string.IsNullOrEmpty(connectorFunction.Visibility)) + { + func.Visibility = connectorFunction.Visibility; + } + + func.ReturnType = connectorFunction.ReturnType._type.ToString(); + func.ReturnType_Detailed = connectorFunction.ConnectorReturnType == null ? (dynamic)"null" : connectorFunction.ConnectorReturnType.ToExpando(noname: true); + + func.ArityMin = connectorFunction.ArityMin; + func.ArityMax = connectorFunction.ArityMax; + func.RequiredParameters = connectorFunction.RequiredParameters == null ? (dynamic)"null" : connectorFunction.RequiredParameters.Select(rp => rp.ToExpando()).ToList(); + func.OptionalParameters = connectorFunction.OptionalParameters == null ? (dynamic)"null" : connectorFunction.OptionalParameters.Select(op => op.ToExpando()).ToList(); + + return func; + } + + internal static ExpandoObject ToExpando(this ConnectorParameter connectorParam) + { + dynamic cParam = new ExpandoObject(); + + cParam.Name = connectorParam.Name; + + if (!string.IsNullOrEmpty(connectorParam.Description)) + { + cParam.Description = connectorParam.Description; + } + + if (connectorParam.Location != null) + { + cParam.Location = connectorParam.Location.ToString(); + } + + cParam.FormulaType = connectorParam.FormulaType._type.ToString(); + cParam.Type = connectorParam.ConnectorType.ToExpando(); + + if (connectorParam.DefaultValue != null) + { + cParam.DefaultValue = connectorParam.DefaultValue.ToExpression(); + } + + if (!string.IsNullOrEmpty(connectorParam.Title)) + { + cParam.Title = connectorParam.Title; + } + + if (connectorParam.ConnectorExtensions.ExplicitInput) + { + cParam.ExplicitInput = connectorParam.ConnectorExtensions.ExplicitInput; + } + + return cParam; + } + + internal static ExpandoObject ToExpando(this ConnectorType connectorType, bool noname = false) + { + dynamic cType = new ExpandoObject(); + + if (!noname) + { + cType.Name = connectorType.Name; + + if (!string.IsNullOrEmpty(connectorType.DisplayName)) + { + cType.DisplayName = connectorType.DisplayName; + } + + if (!string.IsNullOrEmpty(connectorType.Description)) + { + cType.Description = connectorType.Description; + } + } + + cType.IsRequired = connectorType.IsRequired; + + if (connectorType.Fields != null && connectorType.Fields.Any()) + { + cType.Fields = connectorType.Fields.Select(fieldCT => fieldCT.ToExpando()).ToList(); + } + + cType.FormulaType = connectorType.FormulaType._type.ToString(); + + if (connectorType.ExplicitInput) + { + cType.ExplicitInput = connectorType.ExplicitInput; + } + + cType.IsEnum = connectorType.IsEnum; + + if (connectorType.IsEnum) + { + bool hasDisplayNames = connectorType.EnumDisplayNames != null && connectorType.EnumDisplayNames.Length > 0; + cType.EnumValues = connectorType.EnumValues.Select((ev, i) => GetEnumExpando(hasDisplayNames ? connectorType.EnumDisplayNames[i] : null, ev.ToObject().ToString(), ev.Type._type.ToString())).ToList(); + } + + if (connectorType.Visibility != Visibility.None && connectorType.Visibility != Visibility.Unknown) + { + cType.Visibility = connectorType.Visibility.ToString(); + } + + if (connectorType.DynamicList != null) + { + cType.DynamicList = connectorType.DynamicList.ToExpando(); + } + + if (connectorType.DynamicProperty != null) + { + cType.DynamicProperty = connectorType.DynamicProperty.ToExpando(); + } + + if (connectorType.DynamicSchema != null) + { + cType.DynamicSchema = connectorType.DynamicSchema.ToExpando(); + } + + if (connectorType.DynamicValues != null) + { + cType.DynamicValues = connectorType.DynamicValues.ToExpando(); + } + + return cType; + } + + internal static ExpandoObject GetEnumExpando(string displayName, string value, string type) + { + dynamic e = new ExpandoObject(); + + if (!string.IsNullOrEmpty(displayName)) + { + e.DisplayName = displayName; + } + + e.Value = value; + e.Type = type; + + return e; + } + + internal static ExpandoObject ToExpando(this ConnectorDynamicList dynamicList) + { + dynamic dList = new ExpandoObject(); + + dList.OperationId = dynamicList.OperationId; + + if (!string.IsNullOrEmpty(dynamicList.ItemValuePath)) + { + dList.ItemValuePath = dynamicList.ItemValuePath; + } + + if (!string.IsNullOrEmpty(dynamicList.ItemPath)) + { + dList.ItemPath = dynamicList.ItemPath; + } + + if (!string.IsNullOrEmpty(dynamicList.ItemTitlePath)) + { + dList.ItemTitlePath = dynamicList.ItemTitlePath; + } + + if (dynamicList.ParameterMap != null) + { + dList.Map = dynamicList.ParameterMap.ToExpando(); + } + + return dList; + } + + internal static ExpandoObject ToExpando(this ConnectorDynamicProperty dynamicProp) + { + dynamic dProp = new ExpandoObject(); + + dProp.OperationId = dynamicProp.OperationId; + + if (!string.IsNullOrEmpty(dynamicProp.ItemValuePath)) + { + dProp.ItemValuePath = dynamicProp.ItemValuePath; + } + + if (dynamicProp.ParameterMap != null) + { + dProp.Map = dynamicProp.ParameterMap.ToExpando(); + } + + return dProp; + } + + internal static ExpandoObject ToExpando(this ConnectorDynamicSchema dynamicSchema) + { + dynamic dSchema = new ExpandoObject(); + + dSchema.OperationId = dynamicSchema.OperationId; + + if (!string.IsNullOrEmpty(dynamicSchema.ValuePath)) + { + dSchema.ValuePath = dynamicSchema.ValuePath; + } + + if (dynamicSchema.ParameterMap != null) + { + dSchema.Map = dynamicSchema.ParameterMap.ToExpando(); + } + + return dSchema; + } + + internal static ExpandoObject ToExpando(this ConnectorDynamicValue dynamicValue) + { + dynamic dValue = new ExpandoObject(); + + dValue.OperationId = dynamicValue.OperationId; + + if (!string.IsNullOrEmpty(dynamicValue.ValuePath)) + { + dValue.ValuePath = dynamicValue.ValuePath; + } + + if (!string.IsNullOrEmpty(dynamicValue.ValueTitle)) + { + dValue.ValueTitle = dynamicValue.ValueTitle; + } + + if (!string.IsNullOrEmpty(dynamicValue.ValueCollection)) + { + dValue.ValueCollection = dynamicValue.ValueCollection; + } + + if (dynamicValue.ParameterMap != null) + { + dValue.Map = dynamicValue.ParameterMap.ToExpando(); + } + + return dValue; + } + + internal static ExpandoObject ToExpando(this Dictionary paramMap) + { + IDictionary dMap = new ExpandoObject() as IDictionary; + + foreach (var kvp in paramMap) + { + if (kvp.Value is StaticConnectorExtensionValue scev) + { + dMap[kvp.Key] = GetStaticExt(scev.Value.ToExpression(), scev.Value.Type._type.ToString()); + } + else if (kvp.Value is DynamicConnectorExtensionValue dcev) + { + dMap[kvp.Key] = GetDynamicExt(dcev.Reference); + } + } + + return (dynamic)dMap; + } + + internal static ExpandoObject GetStaticExt(string value, string type) + { + dynamic s = new ExpandoObject(); + + s.Value = value; + s.Type = type; + + return s; + } + + internal static ExpandoObject GetDynamicExt(string @ref) + { + dynamic s = new ExpandoObject(); + + s.Reference = @ref; + + return s; + } } } diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/MergeRecordTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests/MergeRecordTests.cs index 832f6b7ed..347b69d45 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests/MergeRecordTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests/MergeRecordTests.cs @@ -102,7 +102,7 @@ namespace Microsoft.PowerFx.Connectors.Tests RecordValue rv2 = engine.Eval(@"{a:""x""}") as RecordValue; var ex = Assert.Throws(() => HttpFunctionInvoker.MergeRecords(rv1, rv2)); - Assert.Equal("Cannot merge a of type DecimalValue with a of type StringValue", ex.Message); + Assert.Equal("Cannot merge 'a' of type DecimalValue with 'a' of type StringValue", ex.Message); } [Fact] diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Microsoft.PowerFx.Connectors.Tests.csproj b/src/tests/Microsoft.PowerFx.Connectors.Tests/Microsoft.PowerFx.Connectors.Tests.csproj index 41e9d3e2e..88367447a 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests/Microsoft.PowerFx.Connectors.Tests.csproj +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests/Microsoft.PowerFx.Connectors.Tests.csproj @@ -18,6 +18,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive +