diff --git a/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs b/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs index c01b9b2b6a..a5bdcafad3 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs @@ -27,6 +27,8 @@ using Microsoft.PowerFx.Intellisense; using Microsoft.PowerFx.Types; using static Microsoft.PowerFx.Connectors.ConnectorHelperFunctions; + +#pragma warning disable SA1117 namespace Microsoft.PowerFx.Connectors { @@ -292,15 +294,16 @@ public ConnectorType ReturnParameterType // Those properties are only used by HttpFunctionInvoker internal ConnectorParameterInternals _internals = null; - private readonly ConnectorLogger _configurationLogger = null; + private readonly ConnectorLogger _configurationLogger = null; - internal ConnectorFunction(OpenApiOperation openApiOperation, bool isSupported, string notSupportedReason, string name, string operationPath, HttpMethod httpMethod, ConnectorSettings connectorSettings, List functionList, ConnectorLogger configurationLogger, IReadOnlyDictionary globalValues) + internal ConnectorFunction(OpenApiOperation openApiOperation, bool isSupported, string notSupportedReason, string name, string operationPath, HttpMethod httpMethod, ConnectorSettings connectorSettings, List functionList, + ConnectorLogger configurationLogger, IReadOnlyDictionary globalValues) { Operation = openApiOperation; Name = name; OperationPath = operationPath; HttpMethod = httpMethod; - ConnectorSettings = connectorSettings; + ConnectorSettings = connectorSettings; GlobalContext = new ConnectorGlobalContext(functionList ?? throw new ArgumentNullException(nameof(functionList)), globalValues); _configurationLogger = configurationLogger; @@ -1007,21 +1010,24 @@ internal static ConnectorType GetConnectorType(string valuePath, StringValue sv, // Only called by ConnectorTable.GetSchema // Returns a FormulaType with AssociatedDataSources set (done in AddTabularDataSource) - internal static ConnectorType GetCdpTableType(ICdpTableResolver tableResolver, string connectorName, string valuePath, StringValue stringValue, List sqlRelationships, ConnectorCompatibility compatibility, string datasetName, out string name, out string displayName, out TableDelegationInfo delegationInfo) + internal static ConnectorType GetCdpTableType(ICdpTableResolver tableResolver, string connectorName, string tableName, string valuePath, StringValue stringValue, List sqlRelationships, ConnectorCompatibility compatibility, string datasetName, + out string name, out string displayName, out TableDelegationInfo delegationInfo, out IEnumerable optionSets) { // There are some errors when parsing this Json payload but that's not a problem here as we only need x-ms-capabilities parsing to work OpenApiReaderSettings oars = new OpenApiReaderSettings() { RuleSet = DefaultValidationRuleSet }; ISwaggerSchema tableSchema = SwaggerSchema.New(new OpenApiStringReader(oars).ReadFragment(stringValue.Value, OpenApi.OpenApiSpecVersion.OpenApi2_0, out OpenApiDiagnostic _)); ServiceCapabilities serviceCapabilities = tableSchema.GetTableCapabilities(); - ConnectorPermission tablePermission = tableSchema.GetPermission(); - + ConnectorPermission tablePermission = tableSchema.GetPermission(); + JsonElement jsonElement = ExtractFromJson(stringValue, valuePath, out name, out displayName); bool isTableReadOnly = tablePermission == ConnectorPermission.PermissionReadOnly; IList referencedEntities = GetReferenceEntities(connectorName, stringValue); - - ConnectorType connectorType = new ConnectorType(jsonElement, compatibility, sqlRelationships, referencedEntities, datasetName, name, connectorName, tableResolver, serviceCapabilities, isTableReadOnly); + + SymbolTable symbolTable = new SymbolTable(); + ConnectorType connectorType = new ConnectorType(jsonElement, tableName, symbolTable, compatibility, sqlRelationships, referencedEntities, datasetName, name, connectorName, tableResolver, serviceCapabilities, isTableReadOnly); delegationInfo = ((DataSourceInfo)connectorType.FormulaType._type.AssociatedDataSources.First()).DelegationInfo; + optionSets = symbolTable.OptionSets.Select(kvp => kvp.Value); return connectorType; } @@ -1400,7 +1406,7 @@ private ConnectorParameterInternals Initialize() { // Ex: Api-Version hiddenRequired = true; - } + } } else if (ConnectorSettings.Compatibility.ExcludeInternals()) { @@ -1413,8 +1419,8 @@ private ConnectorParameterInternals Initialize() return null; } - ConnectorParameter connectorParameter = errorsAndWarnings.AggregateErrorsAndWarnings(new ConnectorParameter(parameter, ConnectorSettings.Compatibility)); - + ConnectorParameter connectorParameter = errorsAndWarnings.AggregateErrorsAndWarnings(new ConnectorParameter(parameter, ConnectorSettings.Compatibility)); + if (connectorParameter.HiddenRecordType != null) { errorsAndWarnings.AddError("[Internal error] Unexpected HiddenRecordType non-null value"); @@ -1482,11 +1488,11 @@ private ConnectorParameterInternals Initialize() } OpenApiParameter bodyParameter = new OpenApiParameter() { Name = bodyPropertyName, Schema = bodyPropertySchema, Description = requestBody.Description, Required = bodyPropertyRequired, Extensions = bodyPropertySchema.Extensions }; - ConnectorParameter bodyConnectorParameter2 = errorsAndWarnings.AggregateErrorsAndWarnings(new ConnectorParameter(bodyParameter, requestBody, ConnectorSettings.Compatibility)); + ConnectorParameter bodyConnectorParameter2 = errorsAndWarnings.AggregateErrorsAndWarnings(new ConnectorParameter(bodyParameter, requestBody, ConnectorSettings.Compatibility)); openApiBodyParameters.Add(bodyConnectorParameter2, OpenApiExtensions.TryGetOpenApiValue(bodyConnectorParameter2.Schema.Default, null, out FormulaValue defaultValue, errorsAndWarnings) ? defaultValue : null); if (bodyConnectorParameter2.HiddenRecordType != null) - { + { hiddenRequiredParameters.Add(errorsAndWarnings.AggregateErrorsAndWarnings(new ConnectorParameter(bodyParameter, true, ConnectorSettings.Compatibility))); } @@ -1506,7 +1512,7 @@ private ConnectorParameterInternals Initialize() } OpenApiParameter bodyParameter2 = new OpenApiParameter() { Name = bodyName, Schema = bodySchema, Description = requestBody.Description, Required = requestBody.Required, Extensions = bodySchema.Extensions }; - ConnectorParameter bodyConnectorParameter3 = errorsAndWarnings.AggregateErrorsAndWarnings(new ConnectorParameter(bodyParameter2, requestBody, ConnectorSettings.Compatibility)); + ConnectorParameter bodyConnectorParameter3 = errorsAndWarnings.AggregateErrorsAndWarnings(new ConnectorParameter(bodyParameter2, requestBody, ConnectorSettings.Compatibility)); openApiBodyParameters.Add(bodyConnectorParameter3, OpenApiExtensions.TryGetOpenApiValue(bodyConnectorParameter3.Schema.Default, null, out FormulaValue defaultValue, errorsAndWarnings) ? defaultValue : null); if (bodyConnectorParameter3.HiddenRecordType != null) @@ -1526,7 +1532,7 @@ private ConnectorParameterInternals Initialize() OpenApiSchema bodyParameterSchema = new OpenApiSchema() { Type = "string" }; OpenApiParameter bodyParameter3 = new OpenApiParameter() { Name = bodyName, Schema = bodyParameterSchema, Description = "Body", Required = requestBody.Required }; - ConnectorParameter bodyParameter = errorsAndWarnings.AggregateErrorsAndWarnings(new ConnectorParameter(bodyParameter3, requestBody, ConnectorSettings.Compatibility)); + ConnectorParameter bodyParameter = errorsAndWarnings.AggregateErrorsAndWarnings(new ConnectorParameter(bodyParameter3, requestBody, ConnectorSettings.Compatibility)); openApiBodyParameters.Add(bodyParameter, OpenApiExtensions.TryGetOpenApiValue(bodyParameter.Schema.Default, null, out FormulaValue defaultValue, errorsAndWarnings) ? defaultValue : null); List parameterList = requestBody.Required ? requiredParameters : optionalParameters; @@ -1569,8 +1575,8 @@ private ConnectorParameterInternals Initialize() _arityMax = _arityMin + (_optionalParameters.Length == 0 ? 0 : 1); _warnings = new List(); - _returnType = errorsAndWarnings.AggregateErrorsAndWarnings(Operation.GetConnectorReturnType(ConnectorSettings.Compatibility)); - + _returnType = errorsAndWarnings.AggregateErrorsAndWarnings(Operation.GetConnectorReturnType(ConnectorSettings.Compatibility)); + if (IsDeprecated) { _warnings.Add(ConnectorStringResources.WarnDeprecatedFunction); diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Execution/FormulaValueSerializer.cs b/src/libraries/Microsoft.PowerFx.Connectors/Execution/FormulaValueSerializer.cs index 6e0ae26012..048d6ed780 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Execution/FormulaValueSerializer.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Execution/FormulaValueSerializer.cs @@ -283,6 +283,10 @@ private async Task WritePropertyAsync(string propertyName, ISwaggerSchema proper { await WriteBlobValueAsync(bv).ConfigureAwait(false); } + else if (fv is OptionSetValue optionSetValue) + { + WriteStringValue(optionSetValue.Option); + } else { throw new PowerFxConnectorException($"Expected StringValue and got {fv?.GetType()?.Name ?? ""} value, for property {propertyName}"); diff --git a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs index 7df311ffd8..0b240d1ebc 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs @@ -10,6 +10,9 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.PowerFx.Core; +using Microsoft.PowerFx.Core.Binding; +using Microsoft.PowerFx.Core.Binding.BindInfo; using Microsoft.PowerFx.Core.IR; using Microsoft.PowerFx.Core.Utils; using Microsoft.PowerFx.Types; @@ -82,45 +85,58 @@ public static string GetBodyName(this OpenApiRequestBody requestBody) } // Get suggested options values. Returns null if none. - internal static (string[] options, ConnectorErrors errors) GetOptions(this OpenApiParameter openApiParameter) - { - ConnectorErrors errors = new ConnectorErrors(); - - // x-ms-enum-values is: array of { value :string, displayName:string}. + internal static DisplayNameProvider GetEnumValues(this ISwaggerParameter openApiParameter) + { + // x-ms-enum-values is: array of { value: string, displayName: string}. if (openApiParameter.Extensions.TryGetValue(XMsEnumValues, out var enumValues)) { if (enumValues is IList array) - { - var list = new List(array.Count); - + { + SingleSourceDisplayNameProvider displayNameProvider = new SingleSourceDisplayNameProvider(); + foreach (var item in array) - { + { + string logical = null; + string display = null; + if (item is IDictionary obj) - { - // has keys, "value", and "displayName" - if (obj.TryGetValue("value", out IOpenApiAny value)) + { + if (obj.TryGetValue("value", out IOpenApiAny openApiLogical)) { - if (value is OpenApiString str) + if (openApiLogical is OpenApiString logicalStr) { - list.Add(str.Value); - continue; + logical = logicalStr.Value; } - else if (value is OpenApiInteger i) + else if (openApiLogical is OpenApiInteger logicalInt) { - list.Add(i.Value.ToString(CultureInfo.InvariantCulture)); - continue; + logical = logicalInt.Value.ToString(CultureInfo.InvariantCulture); + } + } + + if (obj.TryGetValue("displayName", out IOpenApiAny openApiDisplay)) + { + if (openApiDisplay is OpenApiString displayStr) + { + display = displayStr.Value; + } + else if (openApiDisplay is OpenApiInteger displayInt) + { + display = displayInt.Value.ToString(CultureInfo.InvariantCulture); } + } + + if (!string.IsNullOrEmpty(logical) && !string.IsNullOrEmpty(display)) + { + displayNameProvider = displayNameProvider.AddField(new DName(logical), new DName(display)); } } - - errors.AddError($"Unrecognized {XMsEnumValues} schema ({item.GetType().Name})"); - } - - return (list.ToArray(), errors); + } + + return displayNameProvider.LogicalToDisplayPairs.Any() ? displayNameProvider : null; } } - return (null, null); + return null; } public static bool IsTrigger(this OpenApiOperation op) @@ -342,11 +358,11 @@ internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, FormulaType form internal static string GetVisibility(this ISwaggerExtensions schema) => schema.Extensions.TryGetValue(XMsVisibility, out IOpenApiExtension openApiExt) && openApiExt is OpenApiString openApiStr ? openApiStr.Value : null; - internal static string GetEnumName(this ISwaggerExtensions schema) => schema.Extensions.TryGetValue(XMsEnum, out IOpenApiExtension openApiExt) && + internal static string GetEnumName(this ISwaggerExtensions schema) => schema.Extensions.TryGetValue(XMsEnum, out IOpenApiExtension openApiExt) && openApiExt is SwaggerJsonObject jsonObject && jsonObject.TryGetValue("name", out IOpenApiAny enumName) && - enumName is OpenApiString enumNameStr - ? enumNameStr.Value + enumName is OpenApiString enumNameStr + ? enumNameStr.Value : null; internal static string GetMediaKind(this ISwaggerExtensions schema) => schema.Extensions.TryGetValue(XMsMediaKind, out IOpenApiExtension openApiExt) && openApiExt is OpenApiString openApiStr ? openApiStr.Value : null; @@ -378,11 +394,17 @@ internal class ConnectorTypeGetterSettings internal readonly IList SqlRelationships; internal Stack Chain = new Stack(); internal int Level = 0; + internal readonly SymbolTable OptionSets; + + private readonly string _tableName; - internal ConnectorTypeGetterSettings(ConnectorCompatibility connectorCompatibility, IList sqlRelationships = null) + internal ConnectorTypeGetterSettings(ConnectorCompatibility connectorCompatibility, string tableName, SymbolTable optionSets, IList sqlRelationships = null) { Compatibility = connectorCompatibility; + OptionSets = optionSets; SqlRelationships = sqlRelationships; + + _tableName = tableName; } internal ConnectorTypeGetterSettings Stack(string identifier) @@ -397,11 +419,33 @@ internal void UnStack() Chain.Pop(); Level--; } + + // by default, optionset names will be 'propertyName (tableName)' in CDP case, where propertyName is replaced by x-ms-enum content, when provided + // in non-CDP case, tableName is null and will only be 'propertyName' (or x-ms-enum content) + internal string GetOptionSetName(string optionSetNameBase) + { + string optionSetName = optionSetNameBase; + + if (!string.IsNullOrEmpty(_tableName)) + { + optionSetName += $" ({_tableName})"; + } + + return optionSetName; + } } internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiParameter, ConnectorCompatibility compatibility, IList sqlRelationships = null) { - return openApiParameter.GetConnectorType(new ConnectorTypeGetterSettings(compatibility, sqlRelationships)); + return openApiParameter.GetConnectorType(tableName: null, optionSets: null, compatibility, sqlRelationships); + } + + internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiParameter, string tableName, SymbolTable optionSets, ConnectorCompatibility compatibility, IList sqlRelationships = null) + { + ConnectorTypeGetterSettings settings = new ConnectorTypeGetterSettings(compatibility, tableName, optionSets, sqlRelationships); + ConnectorType connectorType = openApiParameter.GetConnectorType(settings); + + return connectorType; } // See https://swagger.io/docs/specification/data-models/data-types/ @@ -445,12 +489,25 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar return new ConnectorType(schema, openApiParameter, FormulaType.Blob); } + // Try getting enum from 'x-ms-enum-values' + DisplayNameProvider optionSetDisplayNameProvider = openApiParameter.GetEnumValues(); + + if (optionSetDisplayNameProvider != null && (settings.Compatibility.IsCDP() || schema.Format == "enum")) + { + string optionSetName = settings.GetOptionSetName(schema.GetEnumName() ?? openApiParameter.Name); + OptionSet optionSet = new OptionSet(optionSetName, optionSetDisplayNameProvider); + optionSet = settings.OptionSets.TryAddOptionSet(optionSet); + return new ConnectorType(schema, openApiParameter, optionSet.FormulaType); + } + + // Try getting enum from 'enum' if (schema.Enum != null && (settings.Compatibility.IsCDP() || schema.Format == "enum")) { if (schema.Enum.All(e => e is OpenApiString)) { - string enumName = schema.GetEnumName() ?? "enum"; - OptionSet optionSet = new OptionSet(enumName, schema.Enum.Select(e => new DName((e as OpenApiString).Value)).ToDictionary(k => k, e => e).ToImmutableDictionary()); + string optionSetName = settings.GetOptionSetName(schema.GetEnumName() ?? openApiParameter.Name); + OptionSet optionSet = new OptionSet(optionSetName, schema.Enum.Select(e => new DName((e as OpenApiString).Value)).ToDictionary(k => k, e => e).ToImmutableDictionary()); + optionSet = settings.OptionSets.TryAddOptionSet(optionSet); return new ConnectorType(schema, openApiParameter, optionSet.FormulaType); } else @@ -478,7 +535,7 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar case null: case "decimal": case "currency": - return new ConnectorType(schema, openApiParameter, FormulaType.Decimal); + return new ConnectorType(schema, openApiParameter, FormulaType.Decimal); default: return new ConnectorType(error: $"Unsupported type of number: {schema.Format}"); @@ -489,7 +546,7 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar return new ConnectorType(schema, openApiParameter, FormulaType.Number); // Always a boolean (Format not used) - case "boolean": + case "boolean": return new ConnectorType(schema, openApiParameter, FormulaType.Boolean); // OpenAPI spec: Format could be , int32, int64 @@ -665,6 +722,34 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar } } + // If an OptionSet doesn't exist, we add it (and return it) + // If an identical OptionSet exists (same name & list of options), we return it + // Otherwise we throw in case of conflict + internal static OptionSet TryAddOptionSet(this SymbolTable symbolTable, OptionSet optionSet) + { + if (optionSet == null) + { + throw new ArgumentNullException("optionSet"); + } + + string name = optionSet.EntityName; + + // No existing symbols with that name + if (!((INameResolver)symbolTable).Lookup(new DName(name), out NameLookupInfo info, NameLookupPreferences.None)) + { + symbolTable.AddOptionSet(optionSet); + return optionSet; + } + + // Same optionset already present in table + if (info.Kind == BindKind.OptionSet && info.Data is OptionSet existingOptionSet && existingOptionSet.Equals(optionSet)) + { + return existingOptionSet; + } + + throw new InvalidOperationException($"Optionset name conflict ({name})"); + } + internal static RecordType ToRecordType(this List<(string logicalName, string displayName, FormulaType type)> fields) { if (fields == null) @@ -733,7 +818,7 @@ public static HttpMethod ToHttpMethod(this OperationType key) } public static FormulaType GetReturnType(this OpenApiOperation openApiOperation, ConnectorCompatibility compatibility) - { + { ConnectorType connectorType = openApiOperation.GetConnectorReturnType(compatibility); FormulaType ft = connectorType.HasErrors ? ConnectorType.DefaultType : connectorType?.FormulaType ?? new BlankType(); return ft; @@ -761,7 +846,7 @@ internal static ConnectorType GetConnectorReturnType(this OpenApiOperation openA if (response == null) { - // Returns UntypedObject by default, without error + // Returns UntypedObject by default, without error return new ConnectorType(null); } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiParser.cs b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiParser.cs index 08296f46c5..7ef8f65cc8 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiParser.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiParser.cs @@ -21,13 +21,13 @@ public static IEnumerable GetFunctions(string @namespace, Ope { return GetFunctions(@namespace, openApiDocument, null, configurationLogger); } - + public static IEnumerable GetFunctions(string @namespace, OpenApiDocument openApiDocument, IReadOnlyDictionary globalValues, ConnectorLogger configurationLogger = null) { try { - configurationLogger?.LogInformation($"Entering in {nameof(OpenApiParser)}.{nameof(GetFunctions)}, with {nameof(ConnectorSettings)} Namespace {@namespace}"); - IEnumerable functions = GetFunctionsInternal(new ConnectorSettings(@namespace), openApiDocument, configurationLogger, globalValues); + configurationLogger?.LogInformation($"Entering in {nameof(OpenApiParser)}.{nameof(GetFunctions)}, with {nameof(ConnectorSettings)} Namespace {@namespace}"); + IEnumerable functions = GetFunctionsInternal(new ConnectorSettings(@namespace), openApiDocument, configurationLogger, globalValues); configurationLogger?.LogInformation($"Exiting {nameof(OpenApiParser)}.{nameof(GetFunctions)}, with {nameof(ConnectorSettings)} Namespace {@namespace}, returning {functions.Count()} functions"); return functions.Where(f => ShouldIncludeFunction(f)); } @@ -36,19 +36,19 @@ public static IEnumerable GetFunctions(string @namespace, Ope configurationLogger?.LogException(ex, $"Exception in {nameof(OpenApiParser)}.{nameof(GetFunctions)}, {nameof(ConnectorSettings)} Namespace {@namespace}, {LogException(ex)}"); throw; } - } + } public static IEnumerable GetFunctions(ConnectorSettings connectorSettings, OpenApiDocument openApiDocument, ConnectorLogger configurationLogger = null) { return GetFunctions(connectorSettings, openApiDocument, null, configurationLogger); - } - + } + public static IEnumerable GetFunctions(ConnectorSettings connectorSettings, OpenApiDocument openApiDocument, IReadOnlyDictionary globalValues, ConnectorLogger configurationLogger = null) { try { - configurationLogger?.LogInformation($"Entering in {nameof(OpenApiParser)}.{nameof(GetFunctions)}, with {nameof(ConnectorSettings)} {LogConnectorSettings(connectorSettings)}"); - IEnumerable functions = GetFunctionsInternal(connectorSettings, openApiDocument, configurationLogger, globalValues); + configurationLogger?.LogInformation($"Entering in {nameof(OpenApiParser)}.{nameof(GetFunctions)}, with {nameof(ConnectorSettings)} {LogConnectorSettings(connectorSettings)}"); + IEnumerable functions = GetFunctionsInternal(connectorSettings, openApiDocument, configurationLogger, globalValues); configurationLogger?.LogInformation($"Exiting {nameof(OpenApiParser)}.{nameof(GetFunctions)}, with {nameof(ConnectorSettings)} {LogConnectorSettings(connectorSettings)}, returning {functions.Count()} functions"); return functions.Where(f => ShouldIncludeFunction(f, connectorSettings)); } @@ -62,14 +62,14 @@ public static IEnumerable GetFunctions(ConnectorSettings conn public static IEnumerable GetTriggers(string @namespace, OpenApiDocument openApiDocument, ConnectorLogger configurationLogger = null) { return GetTriggers(@namespace, openApiDocument, null, configurationLogger); - } - + } + public static IEnumerable GetTriggers(string @namespace, OpenApiDocument openApiDocument, IReadOnlyDictionary globalValues, ConnectorLogger configurationLogger = null) { try { - configurationLogger?.LogInformation($"Entering in {nameof(OpenApiParser)}.{nameof(GetTriggers)}, with {nameof(ConnectorSettings)} Namespace {@namespace}"); - IEnumerable functions = GetFunctionsInternal(new ConnectorSettings(@namespace), openApiDocument, configurationLogger, globalValues, FunctionType.Trigger); + configurationLogger?.LogInformation($"Entering in {nameof(OpenApiParser)}.{nameof(GetTriggers)}, with {nameof(ConnectorSettings)} Namespace {@namespace}"); + IEnumerable functions = GetFunctionsInternal(new ConnectorSettings(@namespace), openApiDocument, configurationLogger, globalValues, FunctionType.Trigger); configurationLogger?.LogInformation($"Exiting {nameof(OpenApiParser)}.{nameof(GetTriggers)}, with {nameof(ConnectorSettings)} Namespace {@namespace}, returning {functions.Count()} functions"); return functions.Where(f => ShouldIncludeFunction(f)); } @@ -83,14 +83,14 @@ public static IEnumerable GetTriggers(string @namespace, Open public static IEnumerable GetTriggers(ConnectorSettings connectorSettings, OpenApiDocument openApiDocument, ConnectorLogger configurationLogger = null) { return GetTriggers(connectorSettings, openApiDocument, null, configurationLogger); - } - + } + public static IEnumerable GetTriggers(ConnectorSettings connectorSettings, OpenApiDocument openApiDocument, IReadOnlyDictionary globalValues, ConnectorLogger configurationLogger = null) { try { - configurationLogger?.LogInformation($"Entering in {nameof(OpenApiParser)}.{nameof(GetTriggers)}, with {nameof(ConnectorSettings)} {LogConnectorSettings(connectorSettings)}"); - IEnumerable functions = GetFunctionsInternal(connectorSettings, openApiDocument, configurationLogger, globalValues, FunctionType.Trigger); + configurationLogger?.LogInformation($"Entering in {nameof(OpenApiParser)}.{nameof(GetTriggers)}, with {nameof(ConnectorSettings)} {LogConnectorSettings(connectorSettings)}"); + IEnumerable functions = GetFunctionsInternal(connectorSettings, openApiDocument, configurationLogger, globalValues, FunctionType.Trigger); configurationLogger?.LogInformation($"Exiting {nameof(OpenApiParser)}.{nameof(GetTriggers)}, with {nameof(ConnectorSettings)} {LogConnectorSettings(connectorSettings)}, returning {functions.Count()} functions"); return functions.Where(f => ShouldIncludeFunction(f, connectorSettings)); } @@ -493,7 +493,7 @@ private static void ValidateSupportedOpenApiParameters(OpenApiOperation op, ref // Parse an OpenApiDocument and return functions. internal static (List connectorFunctions, List texlFunctions) ParseInternal(ConnectorSettings connectorSettings, OpenApiDocument openApiDocument, ConnectorLogger configurationLogger = null, IReadOnlyDictionary globalValues = null) - { + { List cFunctions = GetFunctionsInternal(connectorSettings, openApiDocument, configurationLogger, globalValues).Where(f => ShouldIncludeFunction(f, connectorSettings)).ToList(); List tFunctions = cFunctions.Select(f => new ConnectorTexlFunction(f)).ToList(); diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorParameter.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorParameter.cs index b69ac9779f..4fde16dcd3 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorParameter.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorParameter.cs @@ -46,6 +46,7 @@ internal ConnectorParameter(OpenApiParameter openApiParameter, IOpenApiExtensibl Location = openApiParameter.In; } + // Intellisense only internal ConnectorParameter(ConnectorParameter connectorParameter, ConnectorType connectorType) : base(connectorParameter, connectorType) { diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSchema.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSchema.cs index 39bf54a073..2c117fe0ed 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSchema.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSchema.cs @@ -30,7 +30,7 @@ public class ConnectorSchema : SupportsConnectorErrors public bool SupportsDynamicIntellisense => ConnectorType.SupportsDynamicIntellisense; public bool? NotificationUrl => ConnectorType.NotificationUrl; - + internal ConnectorSchema(ISwaggerParameter openApiParameter, ISwaggerExtensions bodyExtensions, bool useHiddenTypes, ConnectorCompatibility compatibility) { Schema = openApiParameter.Schema; @@ -40,6 +40,7 @@ internal ConnectorSchema(ISwaggerParameter openApiParameter, ISwaggerExtensions ConnectorExtensions = new ConnectorExtensions(openApiParameter, bodyExtensions); } + // Intellisense only internal ConnectorSchema(ConnectorSchema connectorSchema, ConnectorType connectorType) { Schema = connectorSchema.Schema; diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs index 64b1bed879..b896b3d9da 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs @@ -207,15 +207,16 @@ internal ConnectorType(JsonElement schema, ConnectorCompatibility compatibility, { } - internal ConnectorType(JsonElement schema, ConnectorCompatibility compatibility, IList sqlRelationships, IList referencedEntities, string datasetName, string name, string connectorName, ICdpTableResolver resolver, ServiceCapabilities serviceCapabilities, bool isTableReadOnly) - : this(SwaggerJsonSchema.New(schema), null, new SwaggerParameter(null, true, SwaggerJsonSchema.New(schema), null).GetConnectorType(compatibility, sqlRelationships)) + // Called by ConnectorFunction.GetCdpTableType + internal ConnectorType(JsonElement schema, string tableName, SymbolTable optionSets, ConnectorCompatibility compatibility, IList sqlRelationships, IList referencedEntities, string datasetName, string name, string connectorName, ICdpTableResolver resolver, ServiceCapabilities serviceCapabilities, bool isTableReadOnly) + : this(SwaggerJsonSchema.New(schema), null, new SwaggerParameter(null, true, SwaggerJsonSchema.New(schema), null).GetConnectorType(tableName, optionSets, compatibility, sqlRelationships)) { Name = name; foreach (ConnectorType field in Fields.Where(f => f.Capabilities != null)) - { + { serviceCapabilities.AddColumnCapability(field.Name, field.Capabilities); - } + } FormulaType = new CdpRecordType(this, resolver, ServiceCapabilities.ToDelegationInfo(serviceCapabilities, name, isTableReadOnly, this, datasetName)); } @@ -295,7 +296,7 @@ internal void SetRelationship(SqlRelationship relationship) ExternalTables.Add(relationship.ReferencedTable); RelationshipName = relationship.RelationshipName; ForeignKey = relationship.ReferencedColumnName; - } + } private void AggregateErrors(ConnectorType[] types) { diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableResolver.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableResolver.cs index a28c470542..7a64887298 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableResolver.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableResolver.cs @@ -11,12 +11,16 @@ using Microsoft.PowerFx.Core.Entities; using Microsoft.PowerFx.Types; +#pragma warning disable SA1117 + namespace Microsoft.PowerFx.Connectors { internal class CdpTableResolver : ICdpTableResolver { public ConnectorLogger Logger { get; } + public IEnumerable OptionSets { get; private set; } + private readonly CdpTable _tabularTable; private readonly HttpClient _httpClient; @@ -87,7 +91,12 @@ public async Task ResolveTableAsync(string tableName, Cancellatio string connectorName = _uriPrefix.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[1]; - return ConnectorFunction.GetCdpTableType(this, connectorName, "Schema/Items", FormulaValue.New(text), sqlRelationships, ConnectorCompatibility.CdpCompatibility, _tabularTable.DatasetName, out string name, out string displayName, out TableDelegationInfo delegationInfo); + ConnectorType connectorType = ConnectorFunction.GetCdpTableType(this, connectorName, _tabularTable.TableName, "Schema/Items", FormulaValue.New(text), sqlRelationships, ConnectorCompatibility.CdpCompatibility, _tabularTable.DatasetName, + out string name, out string displayName, out TableDelegationInfo delegationInfo, out IEnumerable optionSets); + + OptionSets = optionSets; + + return connectorType; } private bool IsSql() => _uriPrefix.Contains("/sql/"); diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpTable.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpTable.cs index a2df070b77..73620a3d80 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpTable.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpTable.cs @@ -27,6 +27,8 @@ public class CdpTable : CdpService public override bool IsDelegable => (DelegationInfo?.SortRestriction != null) || (DelegationInfo?.FilterRestriction != null) || (DelegationInfo?.FilterSupportedFunctions != null); + public IEnumerable OptionSets { get; private set; } + internal TableDelegationInfo DelegationInfo => ((DataSourceInfo)TabularTableDescriptor.FormulaType._type.AssociatedDataSources.First()).DelegationInfo; internal override IReadOnlyDictionary Relationships => _relationships; @@ -80,6 +82,7 @@ public virtual async Task InitAsync(HttpClient httpClient, string uriPrefix, Can TabularTableDescriptor = await tableResolver.ResolveTableAsync(TableName, cancellationToken).ConfigureAwait(false); _relationships = TabularTableDescriptor.Relationships; + OptionSets = tableResolver.OptionSets; RecordType = (RecordType)TabularTableDescriptor.FormulaType; } @@ -87,8 +90,7 @@ public virtual async Task InitAsync(HttpClient httpClient, string uriPrefix, Can private async Task InitializeDatasetMetadata(HttpClient httpClient, string uriPrefix, ConnectorLogger logger, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - - CdpDataSource cds = new CdpDataSource(DatasetName); + DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(httpClient, uriPrefix, cancellationToken, logger).ConfigureAwait(false); DatasetMetadata = dm ?? throw new InvalidOperationException("Dataset metadata is not available"); diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/CompatibilityTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/CompatibilityTests.cs index 6a7a8c6e22..fa2ca3fb36 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/CompatibilityTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/CompatibilityTests.cs @@ -33,9 +33,9 @@ public void CompatibilityTest() string text = (string)LoggingTestServer.GetFileText(@"Responses\Compatibility GetSchema.json"); - ConnectorType ctCdp = ConnectorFunction.GetCdpTableType(tableResolver, "name", "schema/items", StringValue.New(text), null, ConnectorCompatibility.CdpCompatibility, "dataset", out _, out _, out _); - ConnectorType ctPa = ConnectorFunction.GetCdpTableType(tableResolver, "name", "schema/items", StringValue.New(text), null, ConnectorCompatibility.PowerAppsCompatibility, "dataset", out _, out _, out _); - ConnectorType ctSw = ConnectorFunction.GetCdpTableType(tableResolver, "name", "schema/items", StringValue.New(text), null, ConnectorCompatibility.SwaggerCompatibility, "dataset", out _, out _, out _); + ConnectorType ctCdp = ConnectorFunction.GetCdpTableType(tableResolver, "name", null, "schema/items", StringValue.New(text), null, ConnectorCompatibility.CdpCompatibility, "dataset", out _, out _, out _, out _); + ConnectorType ctPa = ConnectorFunction.GetCdpTableType(tableResolver, "name", null, "schema/items", StringValue.New(text), null, ConnectorCompatibility.PowerAppsCompatibility, "dataset", out _, out _, out _, out _); + ConnectorType ctSw = ConnectorFunction.GetCdpTableType(tableResolver, "name", null, "schema/items", StringValue.New(text), null, ConnectorCompatibility.SwaggerCompatibility, "dataset", out _, out _, out _, out _); string cdp = ctCdp.FormulaType.ToStringWithDisplayNames(); string pa = ctPa.FormulaType.ToStringWithDisplayNames(); diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/InternalTesting.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/InternalTesting.cs index 43d398e3b8..91c3f81afa 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/InternalTesting.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/InternalTesting.cs @@ -1373,7 +1373,8 @@ public static class Exts public static string GetString(this OpenApiSchema schema) { StringBuilder sb = new StringBuilder(); - schema.GetStringInternal(new ConnectorTypeGetterSettings(0), sb); + SymbolTable optionSets = new SymbolTable(); + schema.GetStringInternal(new ConnectorTypeGetterSettings(0, null, optionSets), sb); return sb.ToString(); } diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.projitems b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.projitems index dc0b202d51..15838da7b3 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.projitems +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.projitems @@ -42,6 +42,7 @@ + diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs index 6145d83298..f77dbd7a9d 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs @@ -962,8 +962,8 @@ public async Task ZD_CdpTabular_GetTables2() Assert.Equal( "r![assignee_id:w, brand_id:w, collaborator_ids:s, created_at:d, custom_fields:s, description:s, due_at:d, external_id:s, followup_ids:s, forum_topic_id:w, group_id:w, has_incidents:b, " + - "id:w, organization_id:w, priority:l, problem_id:w, raw_subject:s, recipient:s, requester_id:w, satisfaction_rating:s, sharing_agreement_ids:s, status:s, subject:s, submitter_id:w, " + - "tags:s, ticket_form_id:w, type:s, updated_at:d, url:s, via:s]", ((CdpRecordType)zdTable.RecordType).ToStringWithDisplayNames()); + "id:w, organization_id:w, priority:l, problem_id:w, raw_subject:s, recipient:s, requester_id:w, satisfaction_rating:s, sharing_agreement_ids:s, status:l, subject:s, submitter_id:w, " + + "tags:s, ticket_form_id:w, type:l, updated_at:d, url:s, via:s]", ((CdpRecordType)zdTable.RecordType).ToStringWithDisplayNames()); SymbolValues symbolValues = new SymbolValues().Add("Tickets", zdTable); RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService(logger); @@ -979,7 +979,10 @@ public async Task ZD_CdpTabular_GetTables2() OptionSetValue priority = Assert.IsType(result); Assert.Equal("normal", priority.Option); - Assert.Equal("normal", priority.DisplayName); + Assert.Equal("Normal", priority.DisplayName); + + Assert.NotNull(connectorTable.OptionSets); + Assert.Equal("priority (tickets), status (tickets), type (tickets)", string.Join(", ", connectorTable.OptionSets.Select(os => os.EntityName.Value).OrderBy(x => x))); } } diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD Tickets GetSchema.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD Tickets GetSchema.json index b46cb8ba2d..7b6d06a2a4 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD Tickets GetSchema.json +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD Tickets GetSchema.json @@ -18,254 +18,308 @@ "type": "array", "items": { "type": "object", - "required": [], "properties": { "id": { + "type": "integer", "title": "id", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, - "type": "integer", "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807, + "minimum": -9223372036854776000, + "maximum": 9223372036854776000, "x-ms-keyOrder": 1, "x-ms-keyType": "primary", "x-ms-permission": "read-only", "x-ms-sort": "none" }, "url": { + "type": "string", "title": "url", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, - "type": "string", "x-ms-permission": "read-only", "x-ms-sort": "none" }, "external_id": { + "type": "string", "title": "external_id", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, - "type": "string", "x-ms-permission": "read-write", "x-ms-sort": "none" }, "type": { + "type": "string", + "x-ms-enum-values": [ + { + "value": "problem", + "displayName": "Problem" + }, + { + "value": "incident", + "displayName": "Incident" + }, + { + "value": "question", + "displayName": "Question" + }, + { + "value": "task", + "displayName": "Task" + } + ], "title": "type", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, - "type": "string", "x-ms-permission": "read-write", "x-ms-sort": "none" }, "subject": { + "type": "string", "title": "subject", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, - "type": "string", "x-ms-permission": "read-write", "x-ms-sort": "none" }, "raw_subject": { + "type": "string", "title": "raw_subject", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, - "type": "string", "x-ms-permission": "read-write", "x-ms-sort": "none" }, "description": { + "type": "string", "title": "description", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, - "type": "string", "x-ms-permission": "read-write", "x-ms-sort": "none" }, "priority": { - "title": "priority", - "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, "type": "string", - "enum": [ - "low", - "normal", - "high", - "urgent" + "x-ms-enum-values": [ + { + "value": "low", + "displayName": "Low" + }, + { + "value": "normal", + "displayName": "Normal" + }, + { + "value": "high", + "displayName": "High" + }, + { + "value": "urgent", + "displayName": "Urgent" + } ], + "title": "priority", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, "x-ms-permission": "read-write", "x-ms-sort": "none" }, "status": { + "type": "string", + "x-ms-enum-values": [ + { + "value": "new", + "displayName": "New" + }, + { + "value": "open", + "displayName": "Open" + }, + { + "value": "pending", + "displayName": "Pending" + }, + { + "value": "hold", + "displayName": "Hold" + }, + { + "value": "solved", + "displayName": "Solved" + }, + { + "value": "closed", + "displayName": "Closed" + } + ], "title": "status", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, - "type": "string", "x-ms-permission": "read-write", "x-ms-sort": "none" }, "recipient": { + "type": "string", "title": "recipient", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, - "type": "string", "x-ms-permission": "read-write", "x-ms-sort": "none" }, "requester_id": { + "type": "integer", "title": "requester_id", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, - "type": "integer", "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807, + "minimum": -9223372036854776000, + "maximum": 9223372036854776000, "x-ms-permission": "read-write", "x-ms-sort": "none" }, "submitter_id": { + "type": "integer", "title": "submitter_id", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, - "type": "integer", "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807, + "minimum": -9223372036854776000, + "maximum": 9223372036854776000, "x-ms-permission": "read-write", "x-ms-sort": "none" }, "assignee_id": { + "type": "integer", "title": "assignee_id", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, - "type": "integer", "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807, + "minimum": -9223372036854776000, + "maximum": 9223372036854776000, "x-ms-permission": "read-write", "x-ms-sort": "none" }, "organization_id": { + "type": "integer", "title": "organization_id", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, - "type": "integer", "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807, + "minimum": -9223372036854776000, + "maximum": 9223372036854776000, "x-ms-permission": "read-write", "x-ms-sort": "none" }, "group_id": { + "type": "integer", "title": "group_id", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, - "type": "integer", "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807, + "minimum": -9223372036854776000, + "maximum": 9223372036854776000, "x-ms-permission": "read-write", "x-ms-sort": "none" }, "collaborator_ids": { - "title": "collaborator_ids", "type": "string", + "title": "collaborator_ids", "x-ms-permission": "read-only", "x-ms-sort": "none" }, "forum_topic_id": { + "type": "integer", "title": "forum_topic_id", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, - "type": "integer", "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807, + "minimum": -9223372036854776000, + "maximum": 9223372036854776000, "x-ms-permission": "read-write", "x-ms-sort": "none" }, "problem_id": { + "type": "integer", "title": "problem_id", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, - "type": "integer", "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807, + "minimum": -9223372036854776000, + "maximum": 9223372036854776000, "x-ms-permission": "read-write", "x-ms-sort": "none" }, "has_incidents": { + "type": "boolean", "title": "has_incidents", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "not", "and", "or" ] }, - "type": "boolean", "x-ms-permission": "read-only", "x-ms-sort": "none" }, "due_at": { + "type": "string", "title": "due_at", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "day", "month", "year", "hour", "minute", "second", "date", "time", "totaloffsetminutes" ] }, - "type": "string", "format": "date-time", "x-ms-permission": "read-write", "x-ms-sort": "none" }, "tags": { - "title": "tags", "type": "string", + "title": "tags", "x-ms-permission": "read-only", "x-ms-sort": "none" }, "via": { - "title": "via", "type": "string", + "title": "via", "x-ms-permission": "read-only", "x-ms-sort": "none" }, "custom_fields": { - "title": "custom_fields", "type": "string", + "title": "custom_fields", "x-ms-permission": "read-only", "x-ms-sort": "none" }, "satisfaction_rating": { - "title": "satisfaction_rating", "type": "string", + "title": "satisfaction_rating", "x-ms-permission": "read-only", "x-ms-sort": "none" }, "sharing_agreement_ids": { - "title": "sharing_agreement_ids", "type": "string", + "title": "sharing_agreement_ids", "x-ms-permission": "read-only", "x-ms-sort": "none" }, "followup_ids": { - "title": "followup_ids", "type": "string", + "title": "followup_ids", "x-ms-permission": "read-only", "x-ms-sort": "none" }, "ticket_form_id": { + "type": "integer", "title": "ticket_form_id", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, - "type": "integer", "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807, + "minimum": -9223372036854776000, + "maximum": 9223372036854776000, "x-ms-permission": "read-write", "x-ms-sort": "none" }, "brand_id": { + "type": "integer", "title": "brand_id", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, - "type": "integer", "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807, + "minimum": -9223372036854776000, + "maximum": 9223372036854776000, "x-ms-permission": "read-write", "x-ms-sort": "none" }, "created_at": { + "type": "string", "title": "created_at", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "day", "month", "year", "hour", "minute", "second", "date", "time", "totaloffsetminutes" ] }, - "type": "string", "format": "date-time", "x-ms-permission": "read-only", "x-ms-sort": "none" }, "updated_at": { + "type": "string", "title": "updated_at", "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "day", "month", "year", "hour", "minute", "second", "date", "time", "totaloffsetminutes" ] }, - "type": "string", "format": "date-time", "x-ms-permission": "read-only", "x-ms-sort": "none" } } - }, - "x-ms-permission": "read-write" + } } -} +} \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/SymbolTableTryAddTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/SymbolTableTryAddTests.cs new file mode 100644 index 0000000000..76f406861e --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/SymbolTableTryAddTests.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Linq; +using Microsoft.PowerFx.Core; +using Microsoft.PowerFx.Core.Utils; +using Xunit; + +namespace Microsoft.PowerFx.Connectors.Tests.Shared +{ + public class SymbolTableTryAddOptionSetTests + { + [Fact] + public void TryAddOptionSet() + { + SymbolTable symbolTable = new SymbolTable(); + + SingleSourceDisplayNameProvider dnp = new SingleSourceDisplayNameProvider(); + dnp = dnp.AddField(new DName("logical1"), new DName("display1")); + dnp = dnp.AddField(new DName("logical2"), new DName("display2")); + + OptionSet os1 = new OptionSet("os1", dnp); + OptionSet os2 = new OptionSet("os1", dnp); + + OptionSet os = symbolTable.TryAddOptionSet(os1); + Assert.Same(os1, os); + + // twice the same, nothing added + os = symbolTable.TryAddOptionSet(os1); + Assert.Same(os1, os); + Assert.Single(symbolTable.OptionSets); + + // still the same, nothing added + os = symbolTable.TryAddOptionSet(os2); + Assert.Same(os1, os); + Assert.Single(symbolTable.OptionSets); + + dnp = dnp.AddField(new DName("logical3"), new DName("display3")); + OptionSet os3 = new OptionSet("os3", dnp); + + // new optionSet + os = symbolTable.TryAddOptionSet(os3); + Assert.NotSame(os, os1); + Assert.Equal(2, symbolTable.OptionSets.Count()); + + // try a name conflict now + OptionSet os4 = new OptionSet("os1", dnp); + + InvalidOperationException ioe = Assert.Throws(() => symbolTable.TryAddOptionSet(os4)); + Assert.Equal("Optionset name conflict (os1)", ioe.Message); + } + } +}