From f4b6ac1bbe0d5ed3bce2dbee41e4c1d4f01214be Mon Sep 17 00:00:00 2001 From: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com> Date: Tue, 19 Nov 2024 07:54:29 +0000 Subject: [PATCH] .Net: ImportPluginFromOpenApiAsync will ignore operations with unsupported content types (#9736) ### Motivation and Context Closes #8971 ### Description Operations with unsupported content types should be ignored rather than causing the import to fail. ### Contribution Checklist - [ ] The code builds clean without any errors or warnings - [ ] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [ ] All unit tests pass, and I have added new tests where possible - [ ] I didn't break anyone :smile: --------- Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> --- .../OpenApi/OpenApiDocumentParser.cs | 19 +++-- .../Functions.UnitTests.csproj | 4 + .../OpenApiKernelPluginFactoryTests.cs | 13 +++ .../TestPlugins/multipart-form-data.json | 83 +++++++++++++++++++ 4 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 dotnet/src/Functions/Functions.UnitTests/OpenApi/TestPlugins/multipart-form-data.json diff --git a/dotnet/src/Functions/Functions.OpenApi/OpenApi/OpenApiDocumentParser.cs b/dotnet/src/Functions/Functions.OpenApi/OpenApi/OpenApiDocumentParser.cs index 49f42451ee40..964c55828337 100644 --- a/dotnet/src/Functions/Functions.OpenApi/OpenApi/OpenApiDocumentParser.cs +++ b/dotnet/src/Functions/Functions.OpenApi/OpenApi/OpenApiDocumentParser.cs @@ -203,7 +203,9 @@ internal static List CreateRestApiOperations(OpenApiDocument d continue; } - var operation = new RestApiOperation( + try + { + var operation = new RestApiOperation( id: operationItem.OperationId, servers: operationServers, path: path, @@ -214,18 +216,23 @@ internal static List CreateRestApiOperations(OpenApiDocument d responses: CreateRestApiOperationExpectedResponses(operationItem.Responses).ToDictionary(static item => item.Item1, static item => item.Item2), securityRequirements: CreateRestApiOperationSecurityRequirements(operationItem.Security) ) - { - Extensions = CreateRestApiOperationExtensions(operationItem.Extensions, logger) - }; + { + Extensions = CreateRestApiOperationExtensions(operationItem.Extensions, logger) + }; - operations.Add(operation); + operations.Add(operation); + } + catch (KernelException ke) + { + logger.LogWarning(ke, "Error occurred creating REST API operation for {OperationId}. Operation will be ignored.", operationItem.OperationId); + } } return operations; } catch (Exception ex) { - logger.LogError(ex, "Error occurred during REST API operation creation."); + logger.LogError(ex, "Fatal error occurred during REST API operation creation."); throw; } } diff --git a/dotnet/src/Functions/Functions.UnitTests/Functions.UnitTests.csproj b/dotnet/src/Functions/Functions.UnitTests/Functions.UnitTests.csproj index 54e0f7cb8ed7..4fa0479b6549 100644 --- a/dotnet/src/Functions/Functions.UnitTests/Functions.UnitTests.csproj +++ b/dotnet/src/Functions/Functions.UnitTests/Functions.UnitTests.csproj @@ -15,6 +15,7 @@ + @@ -67,4 +68,7 @@ + + + \ No newline at end of file diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiKernelPluginFactoryTests.cs b/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiKernelPluginFactoryTests.cs index e68c5537c0ae..f05eab224b5e 100644 --- a/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiKernelPluginFactoryTests.cs +++ b/dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiKernelPluginFactoryTests.cs @@ -543,6 +543,19 @@ public static TheoryData> GenerateSecurity }} }; + [Fact] + public async Task ItShouldCreateFunctionWithMultipartFormDataAsync() + { + // Arrange + var openApiDocument = ResourcePluginsProvider.LoadFromResource("multipart-form-data.json"); + + // Act + var plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync("fakePlugin", openApiDocument, this._executionParameters); + + // Assert + Assert.False(plugin.TryGetFunction("createItem", out var _)); + } + [Fact] public void Dispose() { diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenApi/TestPlugins/multipart-form-data.json b/dotnet/src/Functions/Functions.UnitTests/OpenApi/TestPlugins/multipart-form-data.json new file mode 100644 index 000000000000..883ae811e7a2 --- /dev/null +++ b/dotnet/src/Functions/Functions.UnitTests/OpenApi/TestPlugins/multipart-form-data.json @@ -0,0 +1,83 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "API with Multipart Form Data", + "version": "1.0.0", + "description": "API with Multipart Form Data" + }, + "servers": [ + { + "url": "https://api.example.com" + } + ], + "paths": { + "/api/items": { + "post": { + "operationId": "createItem", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "Value": { + "type": "string" + } + } + }, + "encoding": { + "Value": { + "style": "form" + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/GenericResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/GenericResult" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + } + } + } + }, + "components": { + "schemas": { + "GenericResult": { + "type": "object", + "required": [ "type" ], + "properties": { + "type": { + "type": "string" + } + }, + "discriminator": { + "propertyName": "type" + } + } + } + } +} \ No newline at end of file