From 47254142fcb1881887a33ceb10cfb67bbe4f3d5f Mon Sep 17 00:00:00 2001 From: Brice Lambson Date: Thu, 21 Nov 2024 14:34:33 -0700 Subject: [PATCH 1/2] Support NSwag JSON schema integer formats --- .../OpenApiExtensions.cs | 536 +++++++++--------- 1 file changed, 269 insertions(+), 267 deletions(-) diff --git a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs index 0b240d1ebc..922eb51ec0 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs @@ -1,153 +1,153 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; -using System.Linq; -using System.Net.Http; -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 System.Linq; +using System.Net.Http; +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; -using static Microsoft.PowerFx.Connectors.Constants; - -namespace Microsoft.PowerFx.Connectors -{ - // See definitions for x-ms extensions: - // https://docs.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#x-ms-visibility - public static class OpenApiExtensions +using static Microsoft.PowerFx.Connectors.Constants; + +namespace Microsoft.PowerFx.Connectors +{ + // See definitions for x-ms extensions: + // https://docs.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#x-ms-visibility + public static class OpenApiExtensions { - // Keep these constants all lower case + // Keep these constants all lower case public const string ContentType_TextJson = "text/json"; - public const string ContentType_XWwwFormUrlEncoded = "application/x-www-form-urlencoded"; + public const string ContentType_XWwwFormUrlEncoded = "application/x-www-form-urlencoded"; public const string ContentType_ApplicationJson = "application/json"; public const string ContentType_ApplicationOctetStream = "application/octet-stream"; - public const string ContentType_TextCsv = "text/csv"; - public const string ContentType_TextPlain = "text/plain"; + public const string ContentType_TextCsv = "text/csv"; + public const string ContentType_TextPlain = "text/plain"; public const string ContentType_Any = "*/*"; - public const string ContentType_Multipart = "multipart/form-data"; - - private static readonly IReadOnlyList _knownContentTypes = new string[] - { - ContentType_ApplicationJson, - ContentType_XWwwFormUrlEncoded, + public const string ContentType_Multipart = "multipart/form-data"; + + private static readonly IReadOnlyList _knownContentTypes = new string[] + { + ContentType_ApplicationJson, + ContentType_XWwwFormUrlEncoded, ContentType_TextJson, - ContentType_Multipart + ContentType_Multipart }; - - public static string GetBasePath(this OpenApiDocument openApiDocument, SupportsConnectorErrors errors) => GetUriElement(openApiDocument, (uri) => uri.PathAndQuery, errors); - - public static string GetScheme(this OpenApiDocument openApiDocument, SupportsConnectorErrors errors) => GetUriElement(openApiDocument, (uri) => uri.Scheme, errors); - - public static string GetAuthority(this OpenApiDocument openApiDocument, SupportsConnectorErrors errors) => GetUriElement(openApiDocument, (uri) => uri.Authority, errors); - - private static string GetUriElement(this OpenApiDocument openApiDocument, Func getElement, SupportsConnectorErrors errors) + + public static string GetBasePath(this OpenApiDocument openApiDocument, SupportsConnectorErrors errors) => GetUriElement(openApiDocument, (uri) => uri.PathAndQuery, errors); + + public static string GetScheme(this OpenApiDocument openApiDocument, SupportsConnectorErrors errors) => GetUriElement(openApiDocument, (uri) => uri.Scheme, errors); + + public static string GetAuthority(this OpenApiDocument openApiDocument, SupportsConnectorErrors errors) => GetUriElement(openApiDocument, (uri) => uri.Authority, errors); + + private static string GetUriElement(this OpenApiDocument openApiDocument, Func getElement, SupportsConnectorErrors errors) { - if (openApiDocument?.Servers == null) - { - return null; - } - - // Exclude unsecure servers - var servers = openApiDocument.Servers.Where(srv => srv.Url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)).ToArray(); - - // See https://spec.openapis.org/oas/v3.1.0#server-object - var count = servers.Length; - switch (count) - { + if (openApiDocument?.Servers == null) + { + return null; + } + + // Exclude unsecure servers + var servers = openApiDocument.Servers.Where(srv => srv.Url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)).ToArray(); + + // See https://spec.openapis.org/oas/v3.1.0#server-object + var count = servers.Length; + switch (count) + { case 0: - // None + // None return null; - 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; - var uri = new Uri(fullPath); - return getElement(uri); - - default: + 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; + var uri = new Uri(fullPath); + return getElement(uri); + + default: errors.AddError($"Multiple servers in OpenApiDocument is not supported"); return null; - } - } - - public static string GetBodyName(this OpenApiRequestBody requestBody) - { - return requestBody.Extensions.TryGetValue(XMsBodyName, out IOpenApiExtension value) && value is OpenApiString oas ? oas.Value : "body"; - } - - // Get suggested options values. Returns null if none. - internal static DisplayNameProvider GetEnumValues(this ISwaggerParameter openApiParameter) + } + } + + public static string GetBodyName(this OpenApiRequestBody requestBody) + { + return requestBody.Extensions.TryGetValue(XMsBodyName, out IOpenApiExtension value) && value is OpenApiString oas ? oas.Value : "body"; + } + + // Get suggested options values. Returns null if none. + 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) + if (openApiParameter.Extensions.TryGetValue(XMsEnumValues, out var enumValues)) + { + if (enumValues is IList array) { SingleSourceDisplayNameProvider displayNameProvider = new SingleSourceDisplayNameProvider(); - foreach (var item in array) + foreach (var item in array) { string logical = null; string display = null; - - if (item is IDictionary obj) + + if (item is IDictionary obj) { - if (obj.TryGetValue("value", out IOpenApiAny openApiLogical)) - { - if (openApiLogical is OpenApiString logicalStr) - { + if (obj.TryGetValue("value", out IOpenApiAny openApiLogical)) + { + if (openApiLogical is OpenApiString logicalStr) + { logical = logicalStr.Value; - } + } else if (openApiLogical is OpenApiInteger logicalInt) { logical = logicalInt.Value.ToString(CultureInfo.InvariantCulture); - } + } } - if (obj.TryGetValue("displayName", out IOpenApiAny openApiDisplay)) - { - if (openApiDisplay is OpenApiString displayStr) - { + 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)); - } - } + } + } } - return displayNameProvider.LogicalToDisplayPairs.Any() ? displayNameProvider : null; - } - } - - return null; - } - - public static bool IsTrigger(this OpenApiOperation op) - { - // https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#x-ms-trigger - // Identifies whether the current operation is a trigger that produces a single event. - // The absence of this field means this is an action operation. - return op.Extensions.ContainsKey(XMsTrigger); - } - - internal static bool TryGetDefaultValue(this ISwaggerSchema schema, FormulaType formulaType, out FormulaValue defaultValue, SupportsConnectorErrors errors) + return displayNameProvider.LogicalToDisplayPairs.Any() ? displayNameProvider : null; + } + } + + return null; + } + + public static bool IsTrigger(this OpenApiOperation op) + { + // https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#x-ms-trigger + // Identifies whether the current operation is a trigger that produces a single event. + // The absence of this field means this is an action operation. + return op.Extensions.ContainsKey(XMsTrigger); + } + + internal static bool TryGetDefaultValue(this ISwaggerSchema schema, FormulaType formulaType, out FormulaValue defaultValue, SupportsConnectorErrors errors) { if (schema.Type == "array" && formulaType is TableType tableType && schema.Items != null) { @@ -186,7 +186,7 @@ internal static bool TryGetDefaultValue(this ISwaggerSchema schema, FormulaType { values.Add(new NamedValue(columnName, innerDefaultValue)); } - else + else { values.Add(new NamedValue(columnName, FormulaValue.NewBlank(namedFormulaType.Type))); } @@ -215,7 +215,7 @@ internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, FormulaType form { return false; } - else if (openApiAny is OpenApiString str) + else if (openApiAny is OpenApiString str) { if (formulaType != null && formulaType is not StringType && formulaType is not RecordType) { @@ -233,7 +233,7 @@ internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, FormulaType form { formulaValue = FormulaValue.New(dt); } - else + else { errors.AddError($"Unsupported DateTime format: {str.Value}"); return false; @@ -250,21 +250,21 @@ internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, FormulaType form } formulaValue ??= FormulaValue.New(str.Value); - } - else if (openApiAny is OpenApiInteger intVal) - { + } + else if (openApiAny is OpenApiInteger intVal) + { formulaValue = FormulaValue.New((decimal)intVal.Value); - } - else if (openApiAny is OpenApiDouble dbl) - { + } + else if (openApiAny is OpenApiDouble dbl) + { formulaValue = FormulaValue.New((decimal)dbl.Value); } - else if (openApiAny is OpenApiLong lng) - { + else if (openApiAny is OpenApiLong lng) + { formulaValue = FormulaValue.New((decimal)lng.Value); - } - else if (openApiAny is OpenApiBoolean b) - { + } + else if (openApiAny is OpenApiBoolean b) + { formulaValue = FormulaValue.New(b.Value); } else if (openApiAny is OpenApiFloat flt) @@ -273,7 +273,7 @@ internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, FormulaType form } else if (openApiAny is OpenApiByte by) { - // OpenApi library uses Convert.FromBase64String + // OpenApi library uses Convert.FromBase64String formulaValue = FormulaValue.New(Convert.ToBase64String(by.Value)); } else if (openApiAny is OpenApiDateTime dt && allowOpenApiDateTime) @@ -295,7 +295,7 @@ internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, FormulaType form else if (openApiAny is IList arr) { List lst = new List(); - + foreach (IOpenApiAny element in arr) { FormulaType newType = null; @@ -313,10 +313,10 @@ internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, FormulaType form formulaValue = null; return false; } - + lst.Add(fv); } - + RecordType recordType = RecordType.Empty().Add(TableValue.ValueName, FormulaType.String); IRContext irContext = IRContext.NotInSource(recordType); IEnumerable recordValues = lst.Select(item => new InMemoryRecordValue(irContext, new NamedValue[] { new NamedValue(TableValue.ValueName, item) })); @@ -334,15 +334,15 @@ internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, FormulaType form dvParams[kvp.Key] = fv; } } - + formulaValue = FormulaValue.NewRecordFromFields(dvParams.Select(dvp => new NamedValue(dvp.Key, dvp.Value))); } - else + else { errors.AddError($"Unknown default value type {openApiAny.GetType().FullName}"); return false; - } - + } + return true; } @@ -351,9 +351,9 @@ 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 parameters are not showen to the user. - // They can have a default value or be special cased by the infrastructure (like "connectionId"). + + // 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; @@ -388,7 +388,7 @@ internal static void WhenPresent(this IDictionary apiObj, s } } - internal class ConnectorTypeGetterSettings + internal class ConnectorTypeGetterSettings { internal readonly ConnectorCompatibility Compatibility; internal readonly IList SqlRelationships; @@ -448,8 +448,8 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar return connectorType; } - // See https://swagger.io/docs/specification/data-models/data-types/ - internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiParameter, ConnectorTypeGetterSettings settings) + // See https://swagger.io/docs/specification/data-models/data-types/ + internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiParameter, ConnectorTypeGetterSettings settings) { if (openApiParameter == null) { @@ -458,34 +458,34 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar ISwaggerSchema schema = openApiParameter.Schema; - if (settings.Level == 30) + if (settings.Level == 30) { return new ConnectorType(error: "GetConnectorType() excessive recursion"); - } - - // schema.Format is optional and potentially any string - switch (schema.Type) - { - // OpenAPI spec: Format could be , byte, binary, date, date-time, password - case "string": - - // We don't want to have OptionSets in connections, we'll only get string/number for now in FormulaType - // Anyhow, we'll have schema.Enum content in ConnectorType - - switch (schema.Format) + } + + // schema.Format is optional and potentially any string + switch (schema.Type) + { + // OpenAPI spec: Format could be , byte, binary, date, date-time, password + case "string": + + // We don't want to have OptionSets in connections, we'll only get string/number for now in FormulaType + // Anyhow, we'll have schema.Enum content in ConnectorType + + switch (schema.Format) { case "uri": return new ConnectorType(schema, openApiParameter, FormulaType.String); - case "date": // full-date RFC3339 + case "date": // full-date RFC3339 return new ConnectorType(schema, openApiParameter, FormulaType.Date); - case "date-time": // date-time RFC3339 + case "date-time": // date-time RFC3339 case "date-no-tz": return new ConnectorType(schema, openApiParameter, FormulaType.DateTime); - case "byte": // Base64 string - case "binary": // octet stream + case "byte": // Base64 string + case "binary": // octet stream return new ConnectorType(schema, openApiParameter, FormulaType.Blob); } @@ -554,11 +554,13 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar switch (schema.Format) { case null: + case "byte": case "integer": case "int32": return new ConnectorType(schema, openApiParameter, FormulaType.Decimal); case "int64": + case "uint64": case "unixtime": return new ConnectorType(schema, openApiParameter, FormulaType.Decimal); @@ -719,7 +721,7 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar default: return new ConnectorType(error: $"Unsupported schema type {schema.Type}"); - } + } } // If an OptionSet doesn't exist, we add it (and return it) @@ -795,29 +797,29 @@ internal static string GetDisplayName(string name) { string displayName = name.Replace("{", string.Empty).Replace("}", string.Empty); return displayName; - } - - internal static string GetUniqueIdentifier(this ISwaggerSchema schema) + } + + internal static string GetUniqueIdentifier(this ISwaggerSchema schema) { - return schema.Reference != null ? "R:" + schema.Reference.Id : "T:" + schema.Type; - } - - public static HttpMethod ToHttpMethod(this OperationType key) - { - return key switch - { - OperationType.Get => HttpMethod.Get, - OperationType.Put => HttpMethod.Put, - OperationType.Post => HttpMethod.Post, - OperationType.Delete => HttpMethod.Delete, - OperationType.Options => HttpMethod.Options, - OperationType.Head => HttpMethod.Head, - OperationType.Trace => HttpMethod.Trace, - _ => new HttpMethod(key.ToString()) - }; + return schema.Reference != null ? "R:" + schema.Reference.Id : "T:" + schema.Type; } - - public static FormulaType GetReturnType(this OpenApiOperation openApiOperation, ConnectorCompatibility compatibility) + + public static HttpMethod ToHttpMethod(this OperationType key) + { + return key switch + { + OperationType.Get => HttpMethod.Get, + OperationType.Put => HttpMethod.Put, + OperationType.Post => HttpMethod.Post, + OperationType.Delete => HttpMethod.Delete, + OperationType.Options => HttpMethod.Options, + OperationType.Head => HttpMethod.Head, + OperationType.Trace => HttpMethod.Trace, + _ => new HttpMethod(key.ToString()) + }; + } + + public static FormulaType GetReturnType(this OpenApiOperation openApiOperation, ConnectorCompatibility compatibility) { ConnectorType connectorType = openApiOperation.GetConnectorReturnType(compatibility); FormulaType ft = connectorType.HasErrors ? ConnectorType.DefaultType : connectorType?.FormulaType ?? new BlankType(); @@ -834,20 +836,20 @@ internal static ConnectorType GetConnectorReturnType(this OpenApiOperation openA OpenApiResponses responses = openApiOperation.Responses; OpenApiResponse response = responses.Where(kvp => kvp.Key?.Length == 3 && kvp.Key.StartsWith("2", StringComparison.Ordinal)).OrderBy(kvp => kvp.Key).FirstOrDefault().Value; - if (response == null) - { - // If no 200, but "default", use that. - if (!responses.TryGetValue("default", out response)) - { - // If no default, use the first one we find - response = responses.FirstOrDefault().Value; - } - } - - if (response == null) + if (response == null) + { + // If no 200, but "default", use that. + if (!responses.TryGetValue("default", out response)) + { + // If no default, use the first one we find + response = responses.FirstOrDefault().Value; + } + } + + if (response == null) { // Returns UntypedObject by default, without error - return new ConnectorType(null); + return new ConnectorType(null); } if (response.Content.Count == 0) @@ -879,65 +881,65 @@ internal static ConnectorType GetConnectorReturnType(this OpenApiOperation openA } } - return new ConnectorType(error: $"Unsupported return type - found {string.Join(", ", response.Content.Select(kv4 => kv4.Key))}"); - } - - /// - /// Identifies which ContentType and Schema to use. - /// - /// - /// RequestBody content dictionary of possible content types and associated schemas. - /// When we cannot determine the content type to use. - public static (string ContentType, OpenApiMediaType MediaType) GetContentTypeAndSchema(this IDictionary content) - { - Dictionary list = new (); - - foreach (var ct in _knownContentTypes) - { - if (content.TryGetValue(ct, out var mediaType)) - { - if (ct == ContentType_TextJson && mediaType.Schema.Properties.Any()) - { - continue; - } - - if (ct != ContentType_ApplicationJson) - { - return (ct, mediaType); - } - - // application/json - if (mediaType.Schema.Properties.Any() || mediaType.Schema.Type == "object") - { - return (ct, mediaType); - } - - if (mediaType.Schema.Type == "string") - { - return (ContentType_TextPlain, mediaType); - } - } - } - - // Cannot determine Content-Type - return (null, null); + return new ConnectorType(error: $"Unsupported return type - found {string.Join(", ", response.Content.Select(kv4 => kv4.Key))}"); + } + + /// + /// Identifies which ContentType and Schema to use. + /// + /// + /// RequestBody content dictionary of possible content types and associated schemas. + /// When we cannot determine the content type to use. + public static (string ContentType, OpenApiMediaType MediaType) GetContentTypeAndSchema(this IDictionary content) + { + Dictionary list = new (); + + foreach (var ct in _knownContentTypes) + { + if (content.TryGetValue(ct, out var mediaType)) + { + if (ct == ContentType_TextJson && mediaType.Schema.Properties.Any()) + { + continue; + } + + if (ct != ContentType_ApplicationJson) + { + return (ct, mediaType); + } + + // application/json + if (mediaType.Schema.Properties.Any() || mediaType.Schema.Type == "object") + { + return (ct, mediaType); + } + + if (mediaType.Schema.Type == "string") + { + return (ContentType_TextPlain, mediaType); + } + } + } + + // Cannot determine Content-Type + return (null, null); } public static Visibility ToVisibility(this string visibility) { return string.IsNullOrEmpty(visibility) - ? Visibility.None + ? Visibility.None : Enum.TryParse(visibility, true, out Visibility vis) - ? vis + ? vis : Visibility.Unknown; } public static MediaKind ToMediaKind(this string mediaKind) { return string.IsNullOrEmpty(mediaKind) - ? MediaKind.File + ? MediaKind.File : Enum.TryParse(mediaKind, true, out MediaKind mk) - ? mk + ? mk : MediaKind.Unknown; } @@ -946,13 +948,13 @@ internal static string PageLink(this OpenApiOperation op) ext is IDictionary oao && oao.Any() && oao.First().Key == "nextLinkName" && - oao.First().Value is OpenApiString oas - ? oas.Value + oao.First().Value is OpenApiString oas + ? oas.Value : null; internal static string GetSummary(this ISwaggerExtensions param) { - // https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions + // https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions return param.Extensions != null && param.Extensions.TryGetValue(XMsSummary, out IOpenApiExtension ext) && ext is OpenApiString apiStr ? apiStr.Value : null; } @@ -1031,8 +1033,8 @@ internal static Dictionary GetRelationships(this ISwaggerE return null; } - // Get string content of x-ms-url-encoding parameter extension - // Values can be "double" or "single" - https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#x-ms-url-encoding + // Get string content of x-ms-url-encoding parameter extension + // Values can be "double" or "single" - https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#x-ms-url-encoding internal static bool GetDoubleEncoding(this IOpenApiExtensible param) { return param.Extensions != null && param.Extensions.TryGetValue(XMsUrlEncoding, out IOpenApiExtension ext) && ext is OpenApiString apiStr && apiStr.Value.Equals("double", StringComparison.OrdinalIgnoreCase); @@ -1040,21 +1042,21 @@ internal static bool GetDoubleEncoding(this IOpenApiExtensible param) internal static ConnectorDynamicValue GetDynamicValue(this ISwaggerExtensions param) { - // https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#use-dynamic-values + // https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#use-dynamic-values if (param?.Extensions != null && param.Extensions.TryGetValue(XMsDynamicValues, out IOpenApiExtension ext) && ext is IDictionary apiObj) { - // Parameters is required in the spec but there are examples where it's not specified and we'll support this condition with an empty list + // Parameters is required in the spec but there are examples where it's not specified and we'll support this condition with an empty list IDictionary op_prms = apiObj.TryGetValue("parameters", out IOpenApiAny openApiAny) && openApiAny is IDictionary apiString ? apiString : null; ConnectorDynamicValue cdv = new (op_prms); - // Mandatory operationId for connectors, except when capibility or builtInOperation are defined + // Mandatory operationId for connectors, except when capibility or builtInOperation are defined apiObj.WhenPresent("operationId", (opId) => cdv.OperationId = OpenApiHelperFunctions.NormalizeOperationId(opId)); apiObj.WhenPresent("value-title", (opValTitle) => cdv.ValueTitle = opValTitle); apiObj.WhenPresent("value-path", (opValPath) => cdv.ValuePath = opValPath); apiObj.WhenPresent("value-collection", (opValCollection) => cdv.ValueCollection = opValCollection); - // we don't support BuiltInOperations or capabilities right now - // return null to indicate that the call to get suggestions is not needed for this parameter + // we don't support BuiltInOperations or capabilities right now + // return null to indicate that the call to get suggestions is not needed for this parameter apiObj.WhenPresent("capability", (string op_capStr) => cdv = null); apiObj.WhenPresent("builtInOperation", (string op_bioStr) => cdv = null); @@ -1066,13 +1068,13 @@ internal static ConnectorDynamicValue GetDynamicValue(this ISwaggerExtensions pa internal static ConnectorDynamicList GetDynamicList(this ISwaggerExtensions param) { - // https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#use-dynamic-values + // https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#use-dynamic-values if (param?.Extensions != null && param.Extensions.TryGetValue(XMsDynamicList, out IOpenApiExtension ext) && ext is IDictionary apiObj) { - // Mandatory operationId for connectors + // Mandatory operationId for connectors if (apiObj.TryGetValue("operationId", out IOpenApiAny op_id) && op_id is OpenApiString opId) { - // Parameters is required in the spec but there are examples where it's not specified and we'll support this condition with an empty list + // Parameters is required in the spec but there are examples where it's not specified and we'll support this condition with an empty list IDictionary op_prms = apiObj.TryGetValue("parameters", out IOpenApiAny openApiAny) && openApiAny is IDictionary apiString ? apiString : null; ConnectorDynamicList cdl = new (op_prms) { @@ -1096,7 +1098,7 @@ internal static ConnectorDynamicList GetDynamicList(this ISwaggerExtensions para return cdl; } - else + else { return new ConnectorDynamicList($"Missing mandatory parameters operationId and parameters in {XMsDynamicList} extension"); } @@ -1124,17 +1126,17 @@ internal static Dictionary GetParameterMap(thi if (prm.Value is OpenApiDateTime oadt && fv is DateTimeValue dtv) { - // https://github.com/microsoft/OpenAPI.NET/issues/533 - // https://github.com/microsoft/Power-Fx/pull/1987 - https://github.com/microsoft/Power-Fx/issues/1982 + // https://github.com/microsoft/OpenAPI.NET/issues/533 + // https://github.com/microsoft/Power-Fx/pull/1987 - https://github.com/microsoft/Power-Fx/issues/1982 // api-version, x-ms-api-version, X-GitHub-Api-Version... if (forceString && prm.Key.EndsWith("api-version", StringComparison.OrdinalIgnoreCase)) { fv = FormulaValue.New(dtv.GetConvertedValue(TimeZoneInfo.Utc).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); } - else + else { - // A string like "2022-11-18" is interpreted as a DateTimeOffset - // https://github.com/microsoft/OpenAPI.NET/issues/533 + // A string like "2022-11-18" is interpreted as a DateTimeOffset + // https://github.com/microsoft/OpenAPI.NET/issues/533 errors.AddError($"Unsupported OpenApiDateTime {oadt.Value}"); continue; } @@ -1142,17 +1144,17 @@ internal static Dictionary GetParameterMap(thi if (prm.Value is OpenApiDate oad && fv is DateValue dv) { - // https://github.com/microsoft/OpenAPI.NET/issues/533 - // https://github.com/microsoft/Power-Fx/pull/1987 - https://github.com/microsoft/Power-Fx/issues/1982 + // https://github.com/microsoft/OpenAPI.NET/issues/533 + // https://github.com/microsoft/Power-Fx/pull/1987 - https://github.com/microsoft/Power-Fx/issues/1982 // api-version, x-ms-api-version, X-GitHub-Api-Version... if (forceString && prm.Key.EndsWith("api-version", StringComparison.OrdinalIgnoreCase)) { fv = FormulaValue.New(dv.GetConvertedValue(TimeZoneInfo.Utc).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); } - else + else { - // A string like "2022-11-18" is interpreted as a DateTimeOffset - // https://github.com/microsoft/OpenAPI.NET/issues/533 + // A string like "2022-11-18" is interpreted as a DateTimeOffset + // https://github.com/microsoft/OpenAPI.NET/issues/533 errors.AddError($"Unsupported OpenApiDateTime {oad.Value}"); continue; } @@ -1162,7 +1164,7 @@ internal static Dictionary GetParameterMap(thi { dvParams.Add(prm.Key, new StaticConnectorExtensionValue() { Value = fv }); } - else + else { FormulaValue staticValue = rv.GetField("value"); @@ -1183,7 +1185,7 @@ internal static Dictionary GetParameterMap(thi { dvParams.Add(prm.Key, new DynamicConnectorExtensionValue() { Reference = dynamicStringValue.Value }); } - else + else { errors.AddError($"Invalid dynamic value type for {prm.Value.GetType().FullName}, key = {prm.Key}"); } @@ -1195,13 +1197,13 @@ internal static Dictionary GetParameterMap(thi internal static ConnectorDynamicSchema GetDynamicSchema(this ISwaggerExtensions param) { - // https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#use-dynamic-values + // https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#use-dynamic-values if (param?.Extensions != null && param.Extensions.TryGetValue(XMsDynamicSchema, out IOpenApiExtension ext) && ext is IDictionary apiObj) { - // Mandatory operationId for connectors + // Mandatory operationId for connectors if (apiObj.TryGetValue("operationId", out IOpenApiAny op_id) && op_id is OpenApiString opId) { - // Parameters is required in the spec but there are examples where it's not specified and we'll support this condition with an empty list + // Parameters is required in the spec but there are examples where it's not specified and we'll support this condition with an empty list IDictionary op_prms = apiObj.TryGetValue("parameters", out IOpenApiAny openApiAny) && openApiAny is IDictionary apiString ? apiString : null; ConnectorDynamicSchema cds = new (op_prms) @@ -1216,7 +1218,7 @@ internal static ConnectorDynamicSchema GetDynamicSchema(this ISwaggerExtensions return cds; } - else + else { return new ConnectorDynamicSchema(error: $"Missing mandatory parameters operationId and parameters in {XMsDynamicSchema} extension"); } @@ -1227,13 +1229,13 @@ internal static ConnectorDynamicSchema GetDynamicSchema(this ISwaggerExtensions internal static ConnectorDynamicProperty GetDynamicProperty(this ISwaggerExtensions param) { - // https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#use-dynamic-values + // https://learn.microsoft.com/en-us/connectors/custom-connectors/openapi-extensions#use-dynamic-values if (param?.Extensions != null && param.Extensions.TryGetValue(XMsDynamicProperties, out IOpenApiExtension ext) && ext is IDictionary apiObj) { - // Mandatory operationId for connectors + // Mandatory operationId for connectors if (apiObj.TryGetValue("operationId", out IOpenApiAny op_id) && op_id is OpenApiString opId) { - // Parameters is required in the spec but there are examples where it's not specified and we'll support this condition with an empty list + // Parameters is required in the spec but there are examples where it's not specified and we'll support this condition with an empty list IDictionary op_prms = apiObj.TryGetValue("parameters", out IOpenApiAny openApiAny) && openApiAny is IDictionary apiString ? apiString : null; ConnectorDynamicProperty cdp = new (op_prms) @@ -1248,7 +1250,7 @@ internal static ConnectorDynamicProperty GetDynamicProperty(this ISwaggerExtensi return cdp; } - else + else { return new ConnectorDynamicProperty(error: $"Missing mandatory parameters operationId and parameters in {XMsDynamicProperties} extension"); } @@ -1266,5 +1268,5 @@ internal static bool IncludeUntypedObjects(this ConnectorCompatibility compatibi compatibility == ConnectorCompatibility.CdpCompatibility; internal static bool IsCDP(this ConnectorCompatibility compatibility) => compatibility == ConnectorCompatibility.CdpCompatibility; - } + } } From 6daedf6182d8ed7a0e5ac79f551f1ab0b14c9a74 Mon Sep 17 00:00:00 2001 From: Brice Lambson Date: Thu, 21 Nov 2024 14:52:39 -0700 Subject: [PATCH 2/2] fixup! Support NSwag JSON schema integer formats --- .../Microsoft.PowerFx.Connectors/OpenApiExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs index 922eb51ec0..23911b4e1e 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs @@ -617,6 +617,9 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar return new ConnectorType(error: $"Unsupported type of array ({arrayType.FormulaType._type.ToAnonymousString()})"); } + case "file": + return new ConnectorType(schema, openApiParameter, FormulaType.Blob); + case "object": case null: // xml