Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ public static OpenApiAiAdaptiveCardExtension Parse(JsonNode source)
public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStartObject();
// Only write the object if there's actual content to write
if (!string.IsNullOrEmpty(DataPath) && !string.IsNullOrEmpty(File) && !string.IsNullOrEmpty(Title))
{
writer.WriteStartObject();
writer.WritePropertyName(nameof(DataPath).ToFirstCharacterLowerCase().ToSnakeCase());
writer.WriteValue(DataPath);
writer.WritePropertyName(nameof(File).ToFirstCharacterLowerCase());
Expand All @@ -77,7 +78,7 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
writer.WritePropertyName(nameof(Url).ToFirstCharacterLowerCase());
writer.WriteValue(Url);
}
writer.WriteEndObject();
}
writer.WriteEndObject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ public static OpenApiAiCapabilitiesExtension Parse(JsonNode source)
public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStartObject();
// Only write the object if there's actual content to write
if (ResponseSemantics != null || Confirmation != null || SecurityInfo != null)
{
writer.WriteStartObject();

if (ResponseSemantics is not null)
{
Expand All @@ -64,8 +65,9 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
writer.WritePropertyName(nameof(SecurityInfo).ToFirstCharacterLowerCase().ToSnakeCase());
WriteSecurityInfo(writer, SecurityInfo);
}

writer.WriteEndObject();
}
writer.WriteEndObject();
}

private static ExtensionConfirmation ParseConfirmation(JsonObject source)
Expand Down
5 changes: 3 additions & 2 deletions src/Kiota.Builder/OpenApiExtensions/OpenApiLogoExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ public static OpenApiLogoExtension Parse(JsonNode source)
public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
{
ArgumentNullException.ThrowIfNull(writer);
writer.WriteStartObject();
// Only write the object if there's actual content to write
if (!string.IsNullOrEmpty(Url))
{
writer.WriteStartObject();
writer.WritePropertyName(nameof(Url).ToFirstCharacterLowerCase());
writer.WriteValue(Url);
writer.WriteEndObject();
}
writer.WriteEndObject();
}
}
8 changes: 7 additions & 1 deletion src/Kiota.Builder/Plugins/PluginsGenerationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,7 @@ rExtRaw is T rExt &&
capabilitiesExtension is OpenApiAiCapabilitiesExtension capabilities)
{
var functionCapabilities = new FunctionCapabilities();
bool hasContent = false;

// Set ResponseSemantics
if (capabilities.ResponseSemantics is not null)
Expand Down Expand Up @@ -1163,6 +1164,7 @@ rExtRaw is T rExt &&
}
responseSemantics.OAuthCardPath = capabilities.ResponseSemantics.OauthCardPath;
functionCapabilities.ResponseSemantics = responseSemantics;
hasContent = true;
}

// Set Confirmation
Expand All @@ -1175,6 +1177,7 @@ rExtRaw is T rExt &&
Body = capabilities.Confirmation.Body,
};
functionCapabilities.Confirmation = confirmation;
hasContent = true;
}

// Set SecurityInfo
Expand All @@ -1185,8 +1188,11 @@ rExtRaw is T rExt &&
DataHandling = capabilities.SecurityInfo.DataHandling,
};
functionCapabilities.SecurityInfo = securityInfo;
hasContent = true;
}
return functionCapabilities;

// Only return the capabilities object if it actually has content
return hasContent ? functionCapabilities : null;
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Kiota.Builder.OpenApiExtensions;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Writers;

Check failure on line 11 in tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiAiAdaptiveCardExtensionTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'Writers' does not exist in the namespace 'Microsoft.OpenApi' (are you missing an assembly reference?)

Check failure on line 11 in tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiAiAdaptiveCardExtensionTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'Writers' does not exist in the namespace 'Microsoft.OpenApi' (are you missing an assembly reference?)
using Moq;
using Xunit;

Expand Down Expand Up @@ -138,4 +139,24 @@
var result = sWriter.ToString();
Assert.Equal("{\"data_path\":\"$.items\",\"file\":\"path_to_file\",\"title\":\"title\",\"url\":\"https://example.com\"}", result);
}

[Fact]
public void WritesNothingForEmptyAdaptiveCard()
{
var value = new OpenApiAiAdaptiveCardExtension
{
DataPath = null,
File = null,
Title = null,
Url = null
};
using var sWriter = new StringWriter();
OpenApiJsonWriter writer = new(sWriter, new OpenApiJsonWriterSettings { Terse = true });

value.Write(writer, OpenApiSpecVersion.OpenApi3_0);
var result = sWriter.ToString();

// When required properties are null/empty, nothing should be written
Assert.Equal(string.Empty, result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Kiota.Builder.OpenApiExtensions;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Writers;

Check failure on line 11 in tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiAiCapabilitiesExtensionTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'Writers' does not exist in the namespace 'Microsoft.OpenApi' (are you missing an assembly reference?)

Check failure on line 11 in tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiAiCapabilitiesExtensionTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'Writers' does not exist in the namespace 'Microsoft.OpenApi' (are you missing an assembly reference?)
using Moq;
using Xunit;

Expand Down Expand Up @@ -236,4 +237,43 @@
Assert.Contains("some data handling", result);

}

[Fact]
public void WritesNothingForEmptyCapabilities()
{
var value = new OpenApiAiCapabilitiesExtension
{
ResponseSemantics = null,
Confirmation = null,
SecurityInfo = null
};
using var sWriter = new StringWriter();
OpenApiJsonWriter writer = new(sWriter, new OpenApiJsonWriterSettings { Terse = true });

value.Write(writer, OpenApiSpecVersion.OpenApi3_0);
var result = sWriter.ToString();

// When all properties are null, nothing should be written
Assert.Equal(string.Empty, result);
}

[Fact]
public void WritesNothingForCapabilitiesWithEmptyNestedObjects()
{
var value = new OpenApiAiCapabilitiesExtension
{
ResponseSemantics = new ExtensionResponseSemantics { DataPath = string.Empty },
Confirmation = new ExtensionConfirmation(),
SecurityInfo = new ExtensionSecurityInfo()
};
using var sWriter = new StringWriter();
OpenApiJsonWriter writer = new(sWriter, new OpenApiJsonWriterSettings { Terse = true });

value.Write(writer, OpenApiSpecVersion.OpenApi3_0);
var result = sWriter.ToString();

// Should write the object structure even if nested objects are empty
// since the properties are not null
Assert.NotEqual(string.Empty, result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System.IO;
using System.Text.Json.Nodes;
using Kiota.Builder.OpenApiExtensions;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Writers;

Check failure on line 5 in tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiLogoExtensionTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'Writers' does not exist in the namespace 'Microsoft.OpenApi' (are you missing an assembly reference?)

Check failure on line 5 in tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiLogoExtensionTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'Writers' does not exist in the namespace 'Microsoft.OpenApi' (are you missing an assembly reference?)
using Xunit;

namespace Kiota.Builder.Tests.OpenApiExtensions;

public sealed class OpenApiLogoExtensionTest
{
[Fact]
public void Parses()
{
var oaiValueRepresentation =
"""
{
"url": "https://example.com/logo.png"
}
""";
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(oaiValueRepresentation));
var oaiValue = JsonNode.Parse(stream);
var value = OpenApiLogoExtension.Parse(oaiValue);

Assert.NotNull(value);
Assert.Equal("https://example.com/logo.png", value.Url);
}

[Fact]
public void Serializes()
{
var value = new OpenApiLogoExtension
{
Url = "https://example.com/logo.png"
};
using var sWriter = new StringWriter();
OpenApiJsonWriter writer = new(sWriter, new OpenApiJsonWriterSettings { Terse = true });

value.Write(writer, OpenApiSpecVersion.OpenApi3_0);
var result = sWriter.ToString();

Assert.Equal("{\"url\":\"https://example.com/logo.png\"}", result);
}

[Fact]
public void WritesNothingForEmptyLogo()
{
var value = new OpenApiLogoExtension
{
Url = null
};
using var sWriter = new StringWriter();
OpenApiJsonWriter writer = new(sWriter, new OpenApiJsonWriterSettings { Terse = true });

value.Write(writer, OpenApiSpecVersion.OpenApi3_0);
var result = sWriter.ToString();

// When Url is null/empty, nothing should be written
Assert.Equal(string.Empty, result);
}

[Fact]
public void WritesNothingForEmptyUrlString()
{
var value = new OpenApiLogoExtension
{
Url = string.Empty
};
using var sWriter = new StringWriter();
OpenApiJsonWriter writer = new(sWriter, new OpenApiJsonWriterSettings { Terse = true });

value.Write(writer, OpenApiSpecVersion.OpenApi3_0);
var result = sWriter.ToString();

// When Url is empty string, nothing should be written
Assert.Equal(string.Empty, result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2567,6 +2567,72 @@ public void GetFirstPartialFileName_ThrowsArgumentException_WhenInputIsNullOrEmp
Assert.Throws<ArgumentException>(() => PluginsGenerationService.GetFirstPartialFileName(string.Empty));
}

[Fact]
public async Task GeneratesManifestWithoutEmptyCapabilitiesAsync()
{
var simpleDescriptionContent =
"""
openapi: 3.0.0
info:
title: test
version: 1.0
description: test description
servers:
- url: http://localhost/
paths:
/test:
get:
operationId: getTest
summary: Get test data
description: description for test path
x-ai-capabilities: {}
responses:
'200':
description: test
""";
var workingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var simpleDescriptionPath = Path.Combine(workingDirectory) + "description.yaml";
await File.WriteAllTextAsync(simpleDescriptionPath, simpleDescriptionContent);
var openAPIDocumentDS = new OpenApiDocumentDownloadService(_httpClient, _logger);
var outputDirectory = Path.Combine(workingDirectory, "output");
var generationConfiguration = new GenerationConfiguration
{
OutputPath = outputDirectory,
OpenAPIFilePath = simpleDescriptionPath,
PluginTypes = [PluginType.APIPlugin],
ClientClassName = "testPlugin",
ApiRootUrl = "http://localhost/",
};
var (openAPIDocumentStream, _) = await openAPIDocumentDS.LoadStreamAsync(simpleDescriptionPath, generationConfiguration, null, false);
var openApiDocument = await openAPIDocumentDS.GetDocumentFromStreamAsync(openAPIDocumentStream, generationConfiguration);
KiotaBuilder.CleanupOperationIdForPlugins(openApiDocument);
var urlTreeNode = OpenApiUrlTreeNode.Create(openApiDocument, Constants.DefaultOpenApiLabel);

var pluginsGenerationService = new PluginsGenerationService(openApiDocument, urlTreeNode, generationConfiguration, workingDirectory, _logger);

var manifestPaths = await pluginsGenerationService.GenerateManifestAsync();

// Verify the manifest was generated
var apiPluginPath = manifestPaths[PluginType.APIPlugin];
Assert.True(File.Exists(apiPluginPath));

// Read and validate the manifest content
var manifestContent = await File.ReadAllTextAsync(apiPluginPath);
using var jsonDocument = JsonDocument.Parse(manifestContent);
var resultingManifest = PluginManifestDocument.Load(jsonDocument.RootElement);

Assert.NotNull(resultingManifest.Document);
Assert.Single(resultingManifest.Document.Functions); // One function should be generated

// Verify that the function does not have a capabilities property when it's empty
var function = resultingManifest.Document.Functions[0];
Assert.Equal("getTest", function.Name);
Assert.Null(function.Capabilities); // Capabilities should be null for empty x-ai-capabilities

// Also verify the JSON doesn't contain "capabilities": {}
Assert.DoesNotContain("\"capabilities\":", manifestContent);
}

#endregion
}

Loading