Skip to content

Commit

Permalink
.Net: Allow to exclude operations when importing and OpenAPI swagger (#…
Browse files Browse the repository at this point in the history
…3452)

### Motivation and Context

When importing OpenAPI plugins, there can be operations not supported by
SK, triggering an exception.


For instance, SK currently supports only "application/json" and
"text/plain" media types, and `kernel.ImportOpenApiPluginFunctionsAsync`
throws an exception when encountering an operation that uses
"multipart/form-data" (`SKException($"Neither of the media types of
{operationId} is supported.")`).

### Description

Add new option to `OpenApiFunctionExecutionParameters` allowing to
exclude some operations, e.g. those that would otherwise cause an
exception.
  • Loading branch information
dluc committed Nov 11, 2023
1 parent 3791635 commit c4525e3
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 8 deletions.
Expand Up @@ -150,7 +150,11 @@ public static class KernelOpenApiPluginExtensions

using (var documentStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(pluginJson)))
{
var operations = await parser.ParseAsync(documentStream, executionParameters?.IgnoreNonCompliantErrors ?? false, cancellationToken).ConfigureAwait(false);
var operations = await parser.ParseAsync(
documentStream,
executionParameters?.IgnoreNonCompliantErrors ?? false,
executionParameters?.OperationsToExclude,
cancellationToken).ConfigureAwait(false);

var runner = new RestApiOperationRunner(
httpClient,
Expand Down
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Functions.OpenAPI.Authentication;
Expand Down Expand Up @@ -54,6 +55,11 @@ public class OpenApiFunctionExecutionParameters
/// </summary>
public bool EnablePayloadNamespacing { get; set; }

/// <summary>
/// Optional list of HTTP operations to skip when importing the OpenAPI document.
/// </summary>
public IList<string> OperationsToExclude { get; set; }

/// <summary>
/// Initializes a new instance of the <see cref="OpenApiFunctionExecutionParameters"/> class.
/// </summary>
Expand All @@ -68,14 +74,16 @@ public class OpenApiFunctionExecutionParameters
/// If false, the operation payload must be provided via the 'payload' context variable.</param>
/// <param name="enablePayloadNamespacing">Determines whether payload parameter names are augmented with namespaces.
/// Namespaces prevent naming conflicts by adding the parent parameter name as a prefix, separated by dots.</param>
/// <param name="operationsToExclude">Optional list of operations not to import, e.g. in case they are not supported</param>
public OpenApiFunctionExecutionParameters(
HttpClient? httpClient = null,
AuthenticateRequestAsyncCallback? authCallback = null,
Uri? serverUrlOverride = null,
string userAgent = Telemetry.HttpUserAgent,
bool ignoreNonCompliantErrors = false,
bool enableDynamicOperationPayload = false,
bool enablePayloadNamespacing = false)
bool enablePayloadNamespacing = false,
IList<string>? operationsToExclude = null)
{
this.HttpClient = httpClient;
this.AuthCallback = authCallback;
Expand All @@ -84,5 +92,6 @@ public class OpenApiFunctionExecutionParameters
this.IgnoreNonCompliantErrors = ignoreNonCompliantErrors;
this.EnableDynamicPayload = enableDynamicOperationPayload;
this.EnablePayloadNamespacing = enablePayloadNamespacing;
this.OperationsToExclude = operationsToExclude ?? new List<string>();
}
}
Expand Up @@ -20,7 +20,12 @@ internal interface IOpenApiDocumentParser
/// <param name="ignoreNonCompliantErrors">Flag indicating whether to ignore non-compliant errors.
/// If set to true, the parser will not throw exceptions for non-compliant documents.
/// Please note that enabling this option may result in incomplete or inaccurate parsing results.</param>
/// <param name="operationsToExclude">Optional list of operations not to import, e.g. in case they are not supported</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>List of rest operations.</returns>
Task<IList<RestApiOperation>> ParseAsync(Stream stream, bool ignoreNonCompliantErrors = false, CancellationToken cancellationToken = default);
Task<IList<RestApiOperation>> ParseAsync(
Stream stream,
bool ignoreNonCompliantErrors = false,
IList<string>? operationsToExclude = null,
CancellationToken cancellationToken = default);
}
Expand Up @@ -37,7 +37,11 @@ public OpenApiDocumentParser(ILoggerFactory? loggerFactory = null)
}

/// <inheritdoc/>
public async Task<IList<RestApiOperation>> ParseAsync(Stream stream, bool ignoreNonCompliantErrors = false, CancellationToken cancellationToken = default)
public async Task<IList<RestApiOperation>> ParseAsync(
Stream stream,
bool ignoreNonCompliantErrors = false,
IList<string>? operationsToExclude = null,
CancellationToken cancellationToken = default)
{
var jsonObject = await this.DowngradeDocumentVersionToSupportedOneAsync(stream, cancellationToken).ConfigureAwait(false);

Expand All @@ -47,7 +51,7 @@ public async Task<IList<RestApiOperation>> ParseAsync(Stream stream, bool ignore

this.AssertReadingSuccessful(result, ignoreNonCompliantErrors);

return ExtractRestApiOperations(result.OpenApiDocument);
return ExtractRestApiOperations(result.OpenApiDocument, operationsToExclude);
}

#region private
Expand Down Expand Up @@ -147,16 +151,17 @@ private async Task<JsonObject> DowngradeDocumentVersionToSupportedOneAsync(Strea
/// Parses an OpenApi document and extracts REST API operations.
/// </summary>
/// <param name="document">The OpenApi document.</param>
/// <param name="operationsToExclude">Optional list of operations not to import, e.g. in case they are not supported</param>
/// <returns>List of Rest operations.</returns>
private static List<RestApiOperation> ExtractRestApiOperations(OpenApiDocument document)
private static List<RestApiOperation> ExtractRestApiOperations(OpenApiDocument document, IList<string>? operationsToExclude = null)
{
var result = new List<RestApiOperation>();

var serverUrl = document.Servers.FirstOrDefault()?.Url;

foreach (var pathPair in document.Paths)
{
var operations = CreateRestApiOperations(serverUrl, pathPair.Key, pathPair.Value);
var operations = CreateRestApiOperations(serverUrl, pathPair.Key, pathPair.Value, operationsToExclude);

result.AddRange(operations);
}
Expand All @@ -170,8 +175,9 @@ private static List<RestApiOperation> ExtractRestApiOperations(OpenApiDocument d
/// <param name="serverUrl">The server url.</param>
/// <param name="path">Rest resource path.</param>
/// <param name="pathItem">Rest resource metadata.</param>
/// <param name="operationsToExclude">Optional list of operations not to import, e.g. in case they are not supported</param>
/// <returns>Rest operation.</returns>
private static List<RestApiOperation> CreateRestApiOperations(string? serverUrl, string path, OpenApiPathItem pathItem)
private static List<RestApiOperation> CreateRestApiOperations(string? serverUrl, string path, OpenApiPathItem pathItem, IList<string>? operationsToExclude = null)
{
var operations = new List<RestApiOperation>();

Expand All @@ -181,6 +187,11 @@ private static List<RestApiOperation> CreateRestApiOperations(string? serverUrl,

var operationItem = operationPair.Value;

if (operationsToExclude != null && operationsToExclude.Contains(operationItem.OperationId, StringComparer.OrdinalIgnoreCase))
{
continue;
}

var operation = new RestApiOperation(
operationItem.OperationId,
string.IsNullOrEmpty(serverUrl) ? null : new Uri(serverUrl),
Expand Down

0 comments on commit c4525e3

Please sign in to comment.