diff --git a/.vscode/launch.json b/.vscode/launch.json
index 34885c1e13..cdb61f49af 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -196,7 +196,10 @@
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll",
- "args": ["search", "microsoft"],
+ "args": [
+ "search",
+ "microsoft"
+ ],
"cwd": "${workspaceFolder}/src/kiota",
"console": "internalConsole",
"stopAtEntry": false
@@ -207,7 +210,10 @@
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll",
- "args": ["search", "test"],
+ "args": [
+ "search",
+ "test"
+ ],
"cwd": "${workspaceFolder}/src/kiota",
"console": "internalConsole",
"stopAtEntry": false
@@ -249,7 +255,11 @@
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll",
- "args": ["info", "-l", "CSharp"],
+ "args": [
+ "info",
+ "-l",
+ "CSharp"
+ ],
"cwd": "${workspaceFolder}/src/kiota",
"console": "internalConsole",
"stopAtEntry": false
@@ -260,7 +270,11 @@
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll",
- "args": ["update", "-o", "${workspaceFolder}/samples"],
+ "args": [
+ "update",
+ "-o",
+ "${workspaceFolder}/samples"
+ ],
"cwd": "${workspaceFolder}/src/kiota",
"console": "internalConsole",
"stopAtEntry": false
@@ -271,7 +285,10 @@
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll",
- "args": ["workspace", "migrate"],
+ "args": [
+ "workspace",
+ "migrate"
+ ],
"cwd": "${workspaceFolder}/samples/msgraph-mail/dotnet",
"console": "internalConsole",
"stopAtEntry": false,
@@ -285,7 +302,10 @@
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll",
- "args": ["client", "generate"],
+ "args": [
+ "client",
+ "generate"
+ ],
"cwd": "${workspaceFolder}/samples/msgraph-mail/dotnet",
"console": "internalConsole",
"stopAtEntry": false,
@@ -330,7 +350,9 @@
"-i",
"**/messages",
"--type",
- "APIManifest"
+ "ApiManifest",
+ "--type",
+ "microsoft"
],
"cwd": "${workspaceFolder}/samples/msgraph-mail/dotnet",
"console": "internalConsole",
@@ -345,7 +367,11 @@
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll",
- "args": ["login", "github", "device"],
+ "args": [
+ "login",
+ "github",
+ "device"
+ ],
"cwd": "${workspaceFolder}/src/kiota",
"console": "internalConsole",
"stopAtEntry": false
@@ -357,4 +383,4 @@
"processId": "${command:pickProcess}"
}
]
-}
+}
\ No newline at end of file
diff --git a/src/Kiota.Builder/Kiota.Builder.csproj b/src/Kiota.Builder/Kiota.Builder.csproj
index faa7c7dd75..e8aebabd07 100644
--- a/src/Kiota.Builder/Kiota.Builder.csproj
+++ b/src/Kiota.Builder/Kiota.Builder.csproj
@@ -47,7 +47,7 @@
-
+
+internal class OpenAPIRuntimeComparer : IEqualityComparer
{
public bool EvaluateFunctions
{
@@ -15,17 +15,18 @@ public bool EvaluateFunctions
}
private static readonly StringIEnumerableDeepComparer _stringIEnumerableDeepComparer = new();
private static readonly AuthComparer _authComparer = new();
+ private static readonly OpenApiRuntimeSpecComparer _openApiRuntimeSpecComparer = new();
///
- public bool Equals(OpenAPIRuntime? x, OpenAPIRuntime? y)
+ public bool Equals(OpenApiRuntime? x, OpenApiRuntime? y)
{
return x == null && y == null || x != null && y != null && GetHashCode(x) == GetHashCode(y);
}
///
- public int GetHashCode([DisallowNull] OpenAPIRuntime obj)
+ public int GetHashCode([DisallowNull] OpenApiRuntime obj)
{
if (obj == null) return 0;
return (EvaluateFunctions ? _stringIEnumerableDeepComparer.GetHashCode(obj.RunForFunctions ?? Enumerable.Empty()) * 7 : 0) +
- obj.Spec.Select(static x => StringComparer.Ordinal.GetHashCode($"{x.Key}:{x.Value}")).Aggregate(0, (acc, next) => acc + next) * 5 +
+ (obj.Spec is null ? 0 : _openApiRuntimeSpecComparer.GetHashCode(obj.Spec) * 5) +
(obj.Auth is null ? 0 : _authComparer.GetHashCode(obj.Auth) * 3);
}
}
diff --git a/src/Kiota.Builder/Plugins/OpenApiRuntimeSpecComparer.cs b/src/Kiota.Builder/Plugins/OpenApiRuntimeSpecComparer.cs
new file mode 100644
index 0000000000..38de08d0cf
--- /dev/null
+++ b/src/Kiota.Builder/Plugins/OpenApiRuntimeSpecComparer.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Microsoft.Plugins.Manifest;
+
+namespace Kiota.Builder.Plugins;
+
+public class OpenApiRuntimeSpecComparer : IEqualityComparer
+{
+ ///
+ public bool Equals(OpenApiRuntimeSpec? x, OpenApiRuntimeSpec? y)
+ {
+ return x == null && y == null || x != null && y != null && GetHashCode(x) == GetHashCode(y);
+ }
+ ///
+ public int GetHashCode([DisallowNull] OpenApiRuntimeSpec obj)
+ {
+ if (obj == null) return 0;
+ return (string.IsNullOrEmpty(obj.Url) ? 0 : StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Url) * 5) +
+ (string.IsNullOrEmpty(obj.ApiDescription) ? 0 : StringComparer.OrdinalIgnoreCase.GetHashCode(obj.ApiDescription) * 3);
+ }
+}
diff --git a/src/Kiota.Builder/Plugins/PluginsGenerationService.cs b/src/Kiota.Builder/Plugins/PluginsGenerationService.cs
index bc0c3e4bf7..09f073b182 100644
--- a/src/Kiota.Builder/Plugins/PluginsGenerationService.cs
+++ b/src/Kiota.Builder/Plugins/PluginsGenerationService.cs
@@ -8,6 +8,8 @@
using Kiota.Builder.Configuration;
using Kiota.Builder.Extensions;
using Kiota.Builder.OpenApiExtensions;
+using Microsoft.Kiota.Abstractions.Extensions;
+using Microsoft.OpenApi.ApiManifest;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Services;
using Microsoft.OpenApi.Writers;
@@ -30,31 +32,52 @@ public PluginsGenerationService(OpenApiDocument document, OpenApiUrlTreeNode ope
Configuration = configuration;
}
private static readonly OpenAPIRuntimeComparer _openAPIRuntimeComparer = new();
- private const string ManifestFileName = "manifest.json";
+ private const string ManifestFileNameSuffix = ".json";
private const string DescriptionRelativePath = "./openapi.yml";
public async Task GenerateManifestAsync(CancellationToken cancellationToken = default)
{
- var manifestOutputPath = Path.Combine(Configuration.OutputPath, ManifestFileName);
- var directory = Path.GetDirectoryName(manifestOutputPath);
+ // write the decription
+ var descriptionFullPath = Path.Combine(Configuration.OutputPath, DescriptionRelativePath);
+ var directory = Path.GetDirectoryName(descriptionFullPath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
Directory.CreateDirectory(directory);
-
- var descriptionFullPath = Path.Combine(Configuration.OutputPath, DescriptionRelativePath);
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
await using var descriptionStream = File.Create(descriptionFullPath, 4096);
await using var fileWriter = new StreamWriter(descriptionStream);
+#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task
var descriptionWriter = new OpenApiYamlWriter(fileWriter);
OAIDocument.SerializeAsV3(descriptionWriter);
descriptionWriter.Flush();
- var pluginDocument = GetManifestDocument(DescriptionRelativePath);
- await using var fileStream = File.Create(manifestOutputPath, 4096);
- await using var writer = new Utf8JsonWriter(fileStream, new JsonWriterOptions { Indented = true });
+ // write the plugins
+ foreach (var pluginType in Configuration.PluginTypes)
+ {
+ var manifestOutputPath = Path.Combine(Configuration.OutputPath, $"{Configuration.ClientClassName.ToLowerInvariant()}-{pluginType.ToString().ToLowerInvariant()}{ManifestFileNameSuffix}");
+#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
+ await using var fileStream = File.Create(manifestOutputPath, 4096);
+ await using var writer = new Utf8JsonWriter(fileStream, new JsonWriterOptions { Indented = true });
#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task
- pluginDocument.Write(writer);
- await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
+
+ switch (pluginType)
+ {
+ case PluginType.Microsoft:
+ var pluginDocument = GetManifestDocument(DescriptionRelativePath);
+ pluginDocument.Write(writer);
+ break;
+ case PluginType.APIManifest:
+ var apiManifest = new ApiManifestDocument("application"); //TODO add application name
+ apiManifest.ApiDependencies.AddOrReplace(Configuration.ClientClassName, Configuration.ToApiDependency(OAIDocument.HashCode ?? string.Empty, TreeNode?.GetRequestInfo().ToDictionary(static x => x.Key, static x => x.Value) ?? []));
+ apiManifest.Write(writer);
+ break;
+ case PluginType.OpenAI://TODO add support for OpenAI plugin type generation
+ // intentional drop to the default case
+ default:
+ throw new NotImplementedException($"The {pluginType} plugin is not implemented.");
+ }
+ await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
}
- private ManifestDocument GetManifestDocument(string openApiDocumentPath)
+ private PluginManifestDocument GetManifestDocument(string openApiDocumentPath)
{
var (runtimes, functions) = GetRuntimesAndFunctionsFromTree(TreeNode, openApiDocumentPath);
var descriptionForHuman = OAIDocument.Info?.Description.CleanupXMLString() is string d && !string.IsNullOrEmpty(d) ? d : $"Description for {OAIDocument.Info?.Title.CleanupXMLString()}";
@@ -64,7 +87,6 @@ private ManifestDocument GetManifestDocument(string openApiDocumentPath)
string? privacyUrl = null;
if (OAIDocument.Info is not null)
{
-
if (OAIDocument.Info.Extensions.TryGetValue(OpenApiDescriptionForModelExtension.Name, out var descriptionExtension) &&
descriptionExtension is OpenApiDescriptionForModelExtension extension &&
!string.IsNullOrEmpty(extension.Description))
@@ -76,7 +98,7 @@ descriptionExtension is OpenApiDescriptionForModelExtension extension &&
if (OAIDocument.Info.Extensions.TryGetValue(OpenApiPrivacyPolicyUrlExtension.Name, out var privacyExtension) && privacyExtension is OpenApiPrivacyPolicyUrlExtension privacy)
privacyUrl = privacy.Privacy;
}
- return new ManifestDocument
+ return new PluginManifestDocument
{
SchemaVersion = "v2",
NameForHuman = OAIDocument.Info?.Title.CleanupXMLString(),
@@ -100,18 +122,21 @@ descriptionExtension is OpenApiDescriptionForModelExtension extension &&
Functions = [.. functions.OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase)]
};
}
- private (OpenAPIRuntime[], Function[]) GetRuntimesAndFunctionsFromTree(OpenApiUrlTreeNode currentNode, string openApiDocumentPath)
+ private (OpenApiRuntime[], Function[]) GetRuntimesAndFunctionsFromTree(OpenApiUrlTreeNode currentNode, string openApiDocumentPath)
{
- var runtimes = new List();
+ var runtimes = new List();
var functions = new List();
if (currentNode.PathItems.TryGetValue(Constants.DefaultOpenApiLabel, out var pathItem))
{
foreach (var operation in pathItem.Operations.Values.Where(static x => !string.IsNullOrEmpty(x.OperationId)))
{
- runtimes.Add(new OpenAPIRuntime
+ runtimes.Add(new OpenApiRuntime
{
- Auth = new Auth("none"),
- Spec = new Dictionary { { "url", openApiDocumentPath } },
+ Auth = new AnonymousAuth(),
+ Spec = new OpenApiRuntimeSpec()
+ {
+ Url = openApiDocumentPath
+ },
RunForFunctions = [operation.OperationId]
});
var oasParameters = operation.Parameters
@@ -123,19 +148,26 @@ descriptionExtension is OpenApiDescriptionForModelExtension extension &&
functions.Add(new Function
{
Name = operation.OperationId,
- Description = operation.Summary.CleanupXMLString() is string summary && !string.IsNullOrEmpty(summary) ? summary : operation.Description.CleanupXMLString(),
- Parameters = oasParameters.Length == 0 ? null :
- new Parameters(
- "object",
- new Properties(oasParameters.ToDictionary(
- static x => x.Name,
- static x => new Property(
- x.Schema.Type ?? string.Empty,
- x.Description.CleanupXMLString(),
- x.Schema.Default?.ToString() ?? string.Empty,
- null), //TODO enums
- StringComparer.OrdinalIgnoreCase)),
- oasParameters.Where(static x => x.Required).Select(static x => x.Name).ToList()),
+ Description =
+ operation.Summary.CleanupXMLString() is string summary && !string.IsNullOrEmpty(summary)
+ ? summary
+ : operation.Description.CleanupXMLString(),
+ Parameters = oasParameters.Length == 0
+ ? null
+ : new Parameters
+ {
+ Type = "object",
+ Properties = new Properties(oasParameters.ToDictionary(
+ static x => x.Name,
+ static x => new FunctionParameter()
+ {
+ Type = x.Schema.Type ?? string.Empty,
+ Description = x.Description.CleanupXMLString(),
+ Default = x.Schema.Default?.ToString() ?? string.Empty,
+ //TODO enums
+ })),
+ Required = oasParameters.Where(static x => x.Required).Select(static x => x.Name).ToList()
+ },
States = GetStatesFromOperation(operation),
});
}
@@ -177,7 +209,7 @@ rExtRaw is T rExt &&
{
return new State
{
- Instructions = instructionsExtractor(rExt).Where(static x => !string.IsNullOrEmpty(x)).Select(static x => x.CleanupXMLString()).ToList()
+ Instructions = new Instructions(instructionsExtractor(rExt).Where(static x => !string.IsNullOrEmpty(x)).Select(static x => x.CleanupXMLString()).ToList())
};
}
return null;
diff --git a/src/kiota/Handlers/Plugin/AddHandler.cs b/src/kiota/Handlers/Plugin/AddHandler.cs
index 2f150fcd82..be662926cd 100644
--- a/src/kiota/Handlers/Plugin/AddHandler.cs
+++ b/src/kiota/Handlers/Plugin/AddHandler.cs
@@ -94,6 +94,5 @@ public override async Task InvokeAsync(InvocationContext context)
#endif
}
}
- throw new NotImplementedException();
}
}
diff --git a/src/kiota/kiota.csproj b/src/kiota/kiota.csproj
index bd261b9694..68e3fec832 100644
--- a/src/kiota/kiota.csproj
+++ b/src/kiota/kiota.csproj
@@ -46,7 +46,7 @@
-
+
diff --git a/tests/Kiota.Builder.Tests/Plugins/OpenAPIRuntimeComparerTests.cs b/tests/Kiota.Builder.Tests/Plugins/OpenAPIRuntimeComparerTests.cs
index 936f073ac3..97808aa1c9 100644
--- a/tests/Kiota.Builder.Tests/Plugins/OpenAPIRuntimeComparerTests.cs
+++ b/tests/Kiota.Builder.Tests/Plugins/OpenAPIRuntimeComparerTests.cs
@@ -17,8 +17,8 @@ public void Defensive()
[Fact]
public void GetsHashCode()
{
- var runtime1 = new OpenAPIRuntime { Spec = new() { { "key1", "value1" } } };
- var runtime2 = new OpenAPIRuntime { Spec = new() { { "key2", "value2" } }, Auth = new() { Type = "type" } };
+ var runtime1 = new OpenApiRuntime { Spec = new() { Url = "url", ApiDescription = "description" } };
+ var runtime2 = new OpenApiRuntime { Spec = new() { Url = "url", ApiDescription = "description" }, Auth = new AnonymousAuth() };
Assert.NotEqual(_comparer.GetHashCode(runtime1), _comparer.GetHashCode(runtime2));
}
}
diff --git a/tests/Kiota.Builder.Tests/Plugins/PluginsGenerationServiceTests.cs b/tests/Kiota.Builder.Tests/Plugins/PluginsGenerationServiceTests.cs
index 83baa0ba06..77e2b4a638 100644
--- a/tests/Kiota.Builder.Tests/Plugins/PluginsGenerationServiceTests.cs
+++ b/tests/Kiota.Builder.Tests/Plugins/PluginsGenerationServiceTests.cs
@@ -33,6 +33,9 @@ public async Task GeneratesManifest()
info:
title: test
version: 1.0
+servers:
+ - url: http://localhost/
+ description: There's no place like home
paths:
/test:
get:
@@ -49,8 +52,9 @@ public async Task GeneratesManifest()
{
OutputPath = outputDirectory,
OpenAPIFilePath = "openapiPath",
- PluginTypes = [PluginType.APIManifest],
+ PluginTypes = [PluginType.Microsoft, PluginType.APIManifest],
ClientClassName = "client",
+ ApiRootUrl = "http://localhost/", //Kiota builder would set this for us
};
var (openAPIDocumentStream, _) = await openAPIDocumentDS.LoadStreamAsync(simpleDescriptionPath, generationConfiguration, null, false);
var openApiDocument = await openAPIDocumentDS.GetDocumentFromStreamAsync(openAPIDocumentStream, generationConfiguration);
@@ -59,7 +63,8 @@ public async Task GeneratesManifest()
var pluginsGenerationService = new PluginsGenerationService(openApiDocument, urlTreeNode, generationConfiguration);
await pluginsGenerationService.GenerateManifestAsync();
- Assert.True(File.Exists(Path.Combine(outputDirectory, "manifest.json")));
+ Assert.True(File.Exists(Path.Combine(outputDirectory, "client-microsoft.json")));
+ Assert.True(File.Exists(Path.Combine(outputDirectory, "client-apimanifest.json")));
Assert.True(File.Exists(Path.Combine(outputDirectory, "openapi.yml")));
}
}