Skip to content

Commit

Permalink
Add modelAsString management in x-ms-enum extension (#2800)
Browse files Browse the repository at this point in the history
  • Loading branch information
LucGenetier authored Jan 2, 2025
1 parent dd757da commit ccaf7c6
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 74 deletions.
89 changes: 58 additions & 31 deletions src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -66,7 +66,7 @@ private static string GetUriElement(this OpenApiDocument openApiDocument, Func<U
// None
return null;

case 1:
case 1:
// This is a full URL that will pull in 'basePath' property from connectors.
// Extract BasePath back out from this.
var fullPath = openApiDocument.Servers[0].Url;
Expand All @@ -85,14 +85,15 @@ public static string GetBodyName(this OpenApiRequestBody requestBody)
}

// Get suggested options values. Returns null if none.
internal static DisplayNameProvider GetEnumValues(this ISwaggerParameter openApiParameter)
internal static (IEnumerable<KeyValuePair<DName, DName>>, bool isNumber) 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<IOpenApiAny> array)
{
SingleSourceDisplayNameProvider displayNameProvider = new SingleSourceDisplayNameProvider();
{
List<KeyValuePair<DName, DName>> list = new List<KeyValuePair<DName, DName>>();
bool isNumber = false;

foreach (var item in array)
{
Expand All @@ -110,6 +111,7 @@ internal static DisplayNameProvider GetEnumValues(this ISwaggerParameter openApi
else if (openApiLogical is OpenApiInteger logicalInt)
{
logical = logicalInt.Value.ToString(CultureInfo.InvariantCulture);
isNumber = true;
}
}

Expand All @@ -122,21 +124,22 @@ internal static DisplayNameProvider GetEnumValues(this ISwaggerParameter openApi
else if (openApiDisplay is OpenApiInteger displayInt)
{
display = displayInt.Value.ToString(CultureInfo.InvariantCulture);
isNumber = true;
}
}

if (!string.IsNullOrEmpty(logical) && !string.IsNullOrEmpty(display))
{
displayNameProvider = displayNameProvider.AddField(new DName(logical), new DName(display));
list.Add(new KeyValuePair<DName, DName>(new DName(logical), new DName(display)));
}
}
}

return displayNameProvider.LogicalToDisplayPairs.Any() ? displayNameProvider : null;
return (list, isNumber);
}
}

return null;

return (null, false);
}

public static bool IsTrigger(this OpenApiOperation op)
Expand Down Expand Up @@ -350,20 +353,25 @@ internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, FormulaType form

internal static string GetVisibility(this IOpenApiExtensible oae) => SwaggerExtensions.New(oae)?.GetVisibility();

internal static string GetEnumName(this IOpenApiExtensible oae) => SwaggerExtensions.New(oae)?.GetEnumName();
internal static (string name, bool modelAsString) GetEnumName(this IOpenApiExtensible oae) => SwaggerExtensions.New(oae)?.GetEnumName() ?? (null, false);

// Internal parameters are not showen to the user.
// They can have a default value or be special cased by the infrastructure (like "connectionId").
internal static bool IsInternal(this ISwaggerExtensions schema) => string.Equals(schema.GetVisibility(), "internal", StringComparison.OrdinalIgnoreCase);

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) &&
openApiExt is SwaggerJsonObject jsonObject &&
jsonObject.TryGetValue("name", out IOpenApiAny enumName) &&
enumName is OpenApiString enumNameStr
? enumNameStr.Value
: null;
internal static (string name, bool modelAsString) GetEnumName(this ISwaggerExtensions schema) => schema.Extensions.TryGetValue(XMsEnum, out IOpenApiExtension openApiExt) &&
openApiExt is IDictionary<string, IOpenApiAny> jsonObject &&
jsonObject.TryGetValue("name", out IOpenApiAny enumName) &&
enumName is OpenApiString enumNameStr
? (enumNameStr.Value, jsonObject.GetModelAsString())
: (null, false);

private static bool GetModelAsString(this IDictionary<string, IOpenApiAny> jsonObject) => jsonObject.TryGetValue("modelAsString", out IOpenApiAny modelAsString) &&
modelAsString is OpenApiBoolean modelAsStringBool
? modelAsStringBool.Value
: false;

internal static string GetMediaKind(this ISwaggerExtensions schema) => schema.Extensions.TryGetValue(XMsMediaKind, out IOpenApiExtension openApiExt) && openApiExt is OpenApiString openApiStr ? openApiStr.Value : null;

Expand Down Expand Up @@ -392,7 +400,7 @@ internal static void WhenPresent(this IDictionary<string, IOpenApiAny> apiObj, s

internal class ConnectorTypeGetterSettings
{
internal readonly ConnectorSettings Settings;
internal readonly ConnectorSettings Settings;
internal Stack<string> Chain = new Stack<string>();
internal int Level = 0;
internal readonly SymbolTable OptionSets;
Expand All @@ -402,7 +410,7 @@ internal class ConnectorTypeGetterSettings
internal ConnectorTypeGetterSettings(ConnectorSettings settings, string tableName, SymbolTable optionSets)
{
Settings = settings;
OptionSets = optionSets;
OptionSets = optionSets;

_tableName = tableName;
}
Expand Down Expand Up @@ -487,7 +495,7 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar
case "byte": // Base64 string
case "binary": // octet stream
return new ConnectorType(schema, openApiParameter, FormulaType.Blob);
}
}

return TryGetOptionSet(openApiParameter, settings) ?? new ConnectorType(schema, openApiParameter, FormulaType.String);

Expand Down Expand Up @@ -529,11 +537,11 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar
case null:
case "byte":
case "integer":
case "int32":
case "int32":
case "int64":
case "uint64":
case "unixtime":
return TryGetOptionSet(openApiParameter, settings) ?? new ConnectorType(schema, openApiParameter, FormulaType.Decimal);
case "unixtime":
return TryGetOptionSet(openApiParameter, settings) ?? new ConnectorType(schema, openApiParameter, FormulaType.Decimal);

default:
return new ConnectorType(error: $"Unsupported type of integer: {schema.Format}");
Expand Down Expand Up @@ -691,14 +699,23 @@ private static ConnectorType TryGetOptionSet(ISwaggerParameter openApiParameter,

if (settings.Settings.Compatibility.IsCDP() || schema.Format == "enum" || settings.Settings.SupportXMsEnumValues)
{
// Try getting enum from 'x-ms-enum-values'
DisplayNameProvider optionSetDisplayNameProvider = openApiParameter.GetEnumValues();
// Try getting enum from 'x-ms-enum-values'
(IEnumerable<KeyValuePair<DName, DName>> list, bool isNumber) = openApiParameter.GetEnumValues();

if (optionSetDisplayNameProvider != null && optionSetDisplayNameProvider.LogicalToDisplayPairs.Any())
if (list != null && list.Any())
{
string optionSetName = settings.GetOptionSetName(schema.GetEnumName() ?? openApiParameter.Name);
OptionSet optionSet = new OptionSet(optionSetName, optionSetDisplayNameProvider);
(string enumName, bool modelAsString) = schema.GetEnumName();
enumName ??= openApiParameter.Name;

string optionSetName = settings.GetOptionSetName(enumName);
OptionSet optionSet = new OptionSet(optionSetName, new SingleSourceDisplayNameProvider(list));
optionSet = settings.OptionSets.TryAddOptionSet(optionSet);

if (modelAsString)
{
return new ConnectorType(schema, openApiParameter, FormulaType.String, list: list, isNumber: isNumber);
}

return new ConnectorType(schema, openApiParameter, optionSet.FormulaType);
}

Expand All @@ -707,9 +724,19 @@ private static ConnectorType TryGetOptionSet(ISwaggerParameter openApiParameter,
{
if (schema.Enum.All(e => e is OpenApiString))
{
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());
(string enumName, bool modelAsString) = schema.GetEnumName();
enumName ??= openApiParameter.Name;

Dictionary<DName, DName> dic = schema.Enum.Select(e => new DName((e as OpenApiString).Value)).ToDictionary(k => k, e => e);
string optionSetName = settings.GetOptionSetName(enumName);
OptionSet optionSet = new OptionSet(optionSetName, dic.ToImmutableDictionary());
optionSet = settings.OptionSets.TryAddOptionSet(optionSet);

if (modelAsString)
{
return new ConnectorType(schema, openApiParameter, FormulaType.String, list: dic);
}

return new ConnectorType(schema, openApiParameter, optionSet.FormulaType);
}
else
Expand Down Expand Up @@ -829,7 +856,7 @@ public static FormulaType GetReturnType(this OpenApiOperation openApiOperation,
}

public static FormulaType GetReturnType(this OpenApiOperation openApiOperation, ConnectorSettings settings)
{
{
ConnectorType connectorType = openApiOperation.GetConnectorReturnType(settings);
FormulaType ft = connectorType.HasErrors ? ConnectorType.DefaultType : connectorType?.FormulaType ?? new BlankType();
return ft;
Expand Down
78 changes: 40 additions & 38 deletions src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public class ConnectorType : SupportsConnectorErrors

internal string ForeignKey { get; set; }

internal ConnectorType(ISwaggerSchema schema, ISwaggerParameter openApiParameter, FormulaType formulaType, ErrorResourceKey warning = default)
internal ConnectorType(ISwaggerSchema schema, ISwaggerParameter openApiParameter, FormulaType formulaType, ErrorResourceKey warning = default, IEnumerable<KeyValuePair<DName, DName>> list = null, bool isNumber = false)
{
Name = openApiParameter?.Name;
IsRequired = openApiParameter?.Required == true;
Expand All @@ -133,35 +133,39 @@ internal ConnectorType(ISwaggerSchema schema, ISwaggerParameter openApiParameter
MediaKind = openApiParameter?.GetMediaKind().ToMediaKind() ?? (Binary ? MediaKind.File : MediaKind.NotBinary);
NotificationUrl = openApiParameter?.GetNotificationUrl();
AiSensitivity = openApiParameter?.GetAiSensitivity().ToAiSensitivity() ?? AiSensitivity.Unknown;
Description = schema.Description;

string summary = schema.GetSummary();
string title = schema.Title;

DisplayName = string.IsNullOrEmpty(title) ? summary : title;
ExplicitInput = schema.GetExplicitInput();
Capabilities = schema.GetColumnCapabilities();
Relationships = schema.GetRelationships(); // x-ms-relationships
KeyType = schema.GetKeyType();
KeyOrder = schema.GetKeyOrder();
Permission = schema.GetPermission();

// We only support one reference for now
// SalesForce only
if (schema.ReferenceTo != null && schema.ReferenceTo.Count == 1)
{
ExternalTables = new List<string>(schema.ReferenceTo);
RelationshipName = schema.RelationshipName;
ForeignKey = null; // SalesForce doesn't provide it, defaults to "Id"
}

if (schema != null)
Fields = Array.Empty<ConnectorType>();
IsEnum = (schema.Enum != null && schema.Enum.Any()) || (list != null && list.Any());

if (IsEnum)
{
Description = schema.Description;

string summary = schema.GetSummary();
string title = schema.Title;

DisplayName = string.IsNullOrEmpty(title) ? summary : title;
ExplicitInput = schema.GetExplicitInput();
Capabilities = schema.GetColumnCapabilities();
Relationships = schema.GetRelationships(); // x-ms-relationships
KeyType = schema.GetKeyType();
KeyOrder = schema.GetKeyOrder();
Permission = schema.GetPermission();

// We only support one reference for now
// SalesForce only
if (schema.ReferenceTo != null && schema.ReferenceTo.Count == 1)
if (list != null && list.Any())
{
ExternalTables = new List<string>(schema.ReferenceTo);
RelationshipName = schema.RelationshipName;
ForeignKey = null; // SalesForce doesn't provide it, defaults to "Id"
EnumValues = list.Select<KeyValuePair<DName, DName>, 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();
}

Fields = Array.Empty<ConnectorType>();
IsEnum = schema.Enum != null && schema.Enum.Any();

if (IsEnum)
else
{
EnumValues = schema.Enum.Select(oaa =>
{
Expand All @@ -172,18 +176,16 @@ internal ConnectorType(ISwaggerSchema schema, ISwaggerParameter openApiParameter

AddError($"Invalid conversion for type {oaa.GetType().Name} in enum");
return FormulaValue.NewBlank();
}).ToArray();
}).ToArray();

// x-ms-enum-display-name
EnumDisplayNames = schema.Extensions != null && schema.Extensions.TryGetValue(XMsEnumDisplayName, out IOpenApiExtension enumNames) && enumNames is IList<IOpenApiAny> oaa
? oaa.Cast<OpenApiString>().Select(oas => oas.Value).ToArray()
: Array.Empty<string>();
? oaa.Cast<OpenApiString>().Select(oas => oas.Value).ToArray()
: Array.Empty<string>();

// x-ms-enum-values
if (!EnumDisplayNames.Any() && formulaType is OptionSetValueType osvt)
{
DisplayNameProvider dnpp = openApiParameter.GetEnumValues();

List<string> displayNames = new List<string>();

// ensure we follow the EnumValues order
Expand All @@ -206,20 +208,20 @@ internal ConnectorType(ISwaggerSchema schema, ISwaggerParameter openApiParameter
EnumDisplayNames = displayNames.ToArray();
}
}
else
{
// those values are null/empty even if x-ms-dynamic-* could be present and would define possible values
EnumValues = Array.Empty<FormulaValue>();
EnumDisplayNames = Array.Empty<string>();
}
}
else
{
// those values are null/empty even if x-ms-dynamic-* could be present and would define possible values
EnumValues = Array.Empty<FormulaValue>();
EnumDisplayNames = Array.Empty<string>();
}

AddWarning(warning);
DynamicSchema = AggregateErrorsAndWarnings(openApiParameter.GetDynamicSchema());
DynamicProperty = AggregateErrorsAndWarnings(openApiParameter.GetDynamicProperty());
DynamicValues = AggregateErrorsAndWarnings(openApiParameter.GetDynamicValue());
DynamicList = AggregateErrorsAndWarnings(openApiParameter.GetDynamicList());
}
}

internal static readonly FormulaType DefaultType = FormulaType.UntypedObject;

Expand Down
Loading

0 comments on commit ccaf7c6

Please sign in to comment.