diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 53739c92f5..0d367a37e3 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -57,6 +57,7 @@ jobs: - "https://developers.pipedrive.com/docs/api/v1/openapi.yaml" - "apisguru::twilio.com:api" - "apisguru::docusign.net" + - "apisguru::github.com:api.github.com" steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 diff --git a/it/config.json b/it/config.json index 39ef0c14d5..e153f64c5a 100644 --- a/it/config.json +++ b/it/config.json @@ -73,6 +73,9 @@ "./tests/Kiota.Builder.IntegrationTests/GeneratesUritemplateHints.yaml": { "MockServerITFolder": "query-params" }, + "apisguru::github.com:api.github.com": { + "MockServerITFolder": "gh" + }, "apisguru::notion.com": { "ExcludePatterns": [ { @@ -325,4 +328,4 @@ } ] } -} +} \ No newline at end of file diff --git a/it/java/gh/pom.xml b/it/java/gh/pom.xml new file mode 100644 index 0000000000..641d5bdc48 --- /dev/null +++ b/it/java/gh/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + io.kiota + kiota-gh-api + 0.1.0-SNAPSHOT + + + 11 + 11 + 11 + UTF-8 + UTF-8 + + 0.12.1 + + + + + com.microsoft.kiota + microsoft-kiota-abstractions + ${kiota-java.version} + + + com.microsoft.kiota + microsoft-kiota-serialization-json + ${kiota-java.version} + + + com.microsoft.kiota + microsoft-kiota-serialization-text + ${kiota-java.version} + + + com.microsoft.kiota + microsoft-kiota-serialization-form + ${kiota-java.version} + + + com.microsoft.kiota + microsoft-kiota-serialization-multipart + ${kiota-java.version} + + + com.microsoft.kiota + microsoft-kiota-http-okHttp + ${kiota-java.version} + + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + + + org.junit.jupiter + junit-jupiter-engine + 5.9.2 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M9 + + + + \ No newline at end of file diff --git a/it/java/gh/src/test/java/GHAPITest.java b/it/java/gh/src/test/java/GHAPITest.java new file mode 100644 index 0000000000..cc9f50ec48 --- /dev/null +++ b/it/java/gh/src/test/java/GHAPITest.java @@ -0,0 +1,23 @@ +import apisdk.ApiClient; +import com.microsoft.kiota.ApiException; +import com.microsoft.kiota.authentication.AnonymousAuthenticationProvider; +import com.microsoft.kiota.http.OkHttpRequestAdapter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.TimeUnit; + +public class BasicAPITest { + + @Test + void basicTest() throws Exception { + var adapter = new OkHttpRequestAdapter(new AnonymousAuthenticationProvider()); + adapter.setBaseUrl("http://127.0.0.1:1080"); + var client = new ApiClient(adapter); + + client.repos().byOrgId("my-owner").byRepoId("my-repo").get(); + client.repos().byOrgId("my-owner").byRepoId("my-repo").generate().post(null); + } + +} diff --git a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs index b531c7f77b..c88bce001d 100644 --- a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text.RegularExpressions; using Kiota.Builder.Configuration; +using Microsoft.Extensions.Logging; using Microsoft.OpenApi.MicrosoftExtensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; @@ -79,7 +80,7 @@ public static IEnumerable GetPathParametersForCurrentSegment(t .Where(static x => x.Value.PathItems.ContainsKey(Constants.DefaultOpenApiLabel)) .SelectMany(x => GetParametersForPathItem(x.Value.PathItems[Constants.DefaultOpenApiLabel], node.DeduplicatedSegment())) .Distinct(); - return Enumerable.Empty(); + return []; } private const char PathNameSeparator = '\\'; [GeneratedRegex(@"-?id\d?}?$", RegexOptions.Singleline | RegexOptions.IgnoreCase, 500)] @@ -268,4 +269,101 @@ public static void AddDeduplicatedSegment(this OpenApiUrlTreeNode openApiUrlTree ArgumentException.ThrowIfNullOrEmpty(newName); openApiUrlTreeNode.AdditionalData.Add(DeduplicatedSegmentKey, [newName]); } + internal static void MergeIndexNodesAtSameLevel(this OpenApiUrlTreeNode node, ILogger logger) + { + var indexNodes = node.Children + .Where(static x => x.Value.IsPathSegmentWithSingleSimpleParameter()) + .OrderBy(static x => x.Key, StringComparer.OrdinalIgnoreCase) + .ToArray(); + if (indexNodes.Length > 1) + { + var indexNode = indexNodes[0]; + node.Children.Remove(indexNode.Key); + var oldSegmentName = indexNode.Value.Segment.Trim('{', '}').CleanupSymbolName(); + var segmentIndex = indexNode.Value.Path.Split('\\', StringSplitOptions.RemoveEmptyEntries).ToList().IndexOf(indexNode.Value.Segment); + var newSegmentParameterName = oldSegmentName.EndsWith("-id", StringComparison.OrdinalIgnoreCase) ? oldSegmentName : $"{{{oldSegmentName}-id}}"; + indexNode.Value.Path = indexNode.Value.Path.Replace(indexNode.Key, newSegmentParameterName, StringComparison.OrdinalIgnoreCase); + indexNode.Value.AddDeduplicatedSegment(newSegmentParameterName); + node.Children.Add(newSegmentParameterName, indexNode.Value); + CopyNodeIntoOtherNode(indexNode.Value, indexNode.Value, indexNode.Key, newSegmentParameterName, logger); + foreach (var child in indexNodes.Except([indexNode])) + { + node.Children.Remove(child.Key); + CopyNodeIntoOtherNode(child.Value, indexNode.Value, child.Key, newSegmentParameterName, logger); + } + ReplaceParameterInPathForAllChildNodes(indexNode.Value, segmentIndex, newSegmentParameterName); + } + + foreach (var child in node.Children.Values) + MergeIndexNodesAtSameLevel(child, logger); + } + private static void ReplaceParameterInPathForAllChildNodes(OpenApiUrlTreeNode node, int parameterIndex, string newParameterName) + { + if (parameterIndex < 0) + return; + foreach (var child in node.Children.Values) + { + var splatPath = child.Path.Split('\\', StringSplitOptions.RemoveEmptyEntries); + if (splatPath.Length > parameterIndex) + { + var oldName = splatPath[parameterIndex]; + splatPath[parameterIndex] = newParameterName; + child.Path = "\\" + string.Join('\\', splatPath); + if (node.PathItems.TryGetValue(Constants.DefaultOpenApiLabel, out var pathItem)) + { + foreach (var pathParameter in pathItem.Parameters + .Union(pathItem.Operations.SelectMany(static x => x.Value.Parameters)) + .Where(x => x.In == ParameterLocation.Path && oldName.Equals(x.Name, StringComparison.Ordinal))) + { + pathParameter.Name = newParameterName; + } + } + } + ReplaceParameterInPathForAllChildNodes(child, parameterIndex, newParameterName); + } + } + private static void CopyNodeIntoOtherNode(OpenApiUrlTreeNode source, OpenApiUrlTreeNode destination, string pathParameterNameToReplace, string pathParameterNameReplacement, ILogger logger) + { + foreach (var child in source.Children) + { + child.Value.Path = child.Value.Path.Replace(pathParameterNameToReplace, pathParameterNameReplacement, StringComparison.OrdinalIgnoreCase); + if (!destination.Children.TryAdd(child.Key, child.Value)) + CopyNodeIntoOtherNode(child.Value, destination.Children[child.Key], pathParameterNameToReplace, pathParameterNameReplacement, logger); + } + pathParameterNameToReplace = pathParameterNameToReplace.Trim('{', '}'); + pathParameterNameReplacement = pathParameterNameReplacement.Trim('{', '}'); + foreach (var pathItem in source.PathItems) + { + foreach (var pathParameter in pathItem + .Value + .Parameters + .Where(x => x.In == ParameterLocation.Path && pathParameterNameToReplace.Equals(x.Name, StringComparison.Ordinal)) + .Union( + pathItem + .Value + .Operations + .SelectMany(static x => x.Value.Parameters) + .Where(x => x.In == ParameterLocation.Path && pathParameterNameToReplace.Equals(x.Name, StringComparison.Ordinal)) + )) + { + pathParameter.Name = pathParameterNameReplacement; + } + if (source != destination && !destination.PathItems.TryAdd(pathItem.Key, pathItem.Value)) + { + var destinationPathItem = destination.PathItems[pathItem.Key]; + foreach (var operation in pathItem.Value.Operations) + if (!destinationPathItem.Operations.TryAdd(operation.Key, operation.Value)) + { + logger.LogWarning("Duplicate operation {Operation} in path {Path}", operation.Key, pathItem.Key); + } + foreach (var pathParameter in pathItem.Value.Parameters) + destinationPathItem.Parameters.Add(pathParameter); + foreach (var extension in pathItem.Value.Extensions) + if (!destinationPathItem.Extensions.TryAdd(extension.Key, extension.Value)) + { + logger.LogWarning("Duplicate extension {Extension} in path {Path}", extension.Key, pathItem.Key); + } + } + } + } } diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 0351366d1b..756afb3228 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -549,80 +549,11 @@ public OpenApiUrlTreeNode CreateUriSpace(OpenApiDocument doc) var stopwatch = new Stopwatch(); stopwatch.Start(); var node = OpenApiUrlTreeNode.Create(doc, Constants.DefaultOpenApiLabel); - MergeIndexNodesAtSameLevel(node); + node.MergeIndexNodesAtSameLevel(logger); stopwatch.Stop(); logger.LogTrace("{Timestamp}ms: Created UriSpace tree", stopwatch.ElapsedMilliseconds); return node; } - private void MergeIndexNodesAtSameLevel(OpenApiUrlTreeNode node) - { - var indexNodes = node.Children - .Where(static x => x.Value.IsPathSegmentWithSingleSimpleParameter()) - .OrderBy(static x => x.Key, StringComparer.OrdinalIgnoreCase) - .ToArray(); - if (indexNodes.Length > 1) - { - var indexNode = indexNodes[0]; - node.Children.Remove(indexNode.Key); - var newSegmentParameterName = $"{{{node.Segment.CleanupSymbolName()}-id}}"; - indexNode.Value.Path = indexNode.Value.Path.Replace(indexNode.Key, newSegmentParameterName, StringComparison.OrdinalIgnoreCase); - indexNode.Value.AddDeduplicatedSegment(newSegmentParameterName); - node.Children.Add(newSegmentParameterName, indexNode.Value); - CopyNodeIntoOtherNode(indexNode.Value, indexNode.Value, indexNode.Key, newSegmentParameterName); - foreach (var child in indexNodes.Except(new[] { indexNode })) - { - node.Children.Remove(child.Key); - CopyNodeIntoOtherNode(child.Value, indexNode.Value, child.Key, newSegmentParameterName); - } - } - - foreach (var child in node.Children.Values) - MergeIndexNodesAtSameLevel(child); - } - private void CopyNodeIntoOtherNode(OpenApiUrlTreeNode source, OpenApiUrlTreeNode destination, string pathParameterNameToReplace, string pathParameterNameReplacement) - { - foreach (var child in source.Children) - { - child.Value.Path = child.Value.Path.Replace(pathParameterNameToReplace, pathParameterNameReplacement, StringComparison.OrdinalIgnoreCase); - if (!destination.Children.TryAdd(child.Key, child.Value)) - CopyNodeIntoOtherNode(child.Value, destination.Children[child.Key], pathParameterNameToReplace, pathParameterNameReplacement); - } - pathParameterNameToReplace = pathParameterNameToReplace.Trim('{', '}'); - pathParameterNameReplacement = pathParameterNameReplacement.Trim('{', '}'); - foreach (var pathItem in source.PathItems) - { - foreach (var pathParameter in pathItem - .Value - .Parameters - .Where(x => x.In == ParameterLocation.Path && pathParameterNameToReplace.Equals(x.Name, StringComparison.Ordinal)) - .Union( - pathItem - .Value - .Operations - .SelectMany(static x => x.Value.Parameters) - .Where(x => x.In == ParameterLocation.Path && pathParameterNameToReplace.Equals(x.Name, StringComparison.Ordinal)) - )) - { - pathParameter.Name = pathParameterNameReplacement; - } - if (source != destination && !destination.PathItems.TryAdd(pathItem.Key, pathItem.Value)) - { - var destinationPathItem = destination.PathItems[pathItem.Key]; - foreach (var operation in pathItem.Value.Operations) - if (!destinationPathItem.Operations.TryAdd(operation.Key, operation.Value)) - { - logger.LogWarning("Duplicate operation {Operation} in path {Path}", operation.Key, pathItem.Key); - } - foreach (var pathParameter in pathItem.Value.Parameters) - destinationPathItem.Parameters.Add(pathParameter); - foreach (var extension in pathItem.Value.Extensions) - if (!destinationPathItem.Extensions.TryAdd(extension.Key, extension.Value)) - { - logger.LogWarning("Duplicate extension {Extension} in path {Path}", extension.Key, pathItem.Key); - } - } - } - } private CodeNamespace? rootNamespace; private CodeNamespace? modelsNamespace; private string? modelNamespacePrefixToTrim; diff --git a/tests/Kiota.Builder.Tests/Extensions/OpenApiUrlTreeNodeExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/OpenApiUrlTreeNodeExtensionsTests.cs index c97dd49ea1..0a7030661a 100644 --- a/tests/Kiota.Builder.Tests/Extensions/OpenApiUrlTreeNodeExtensionsTests.cs +++ b/tests/Kiota.Builder.Tests/Extensions/OpenApiUrlTreeNodeExtensionsTests.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; - +using System.Net.Http; +using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; using Microsoft.OpenApi.Models; @@ -9,7 +11,7 @@ using Xunit; namespace Kiota.Builder.Tests.Extensions; -public class OpenApiUrlTreeNodeExtensionsTests +public sealed class OpenApiUrlTreeNodeExtensionsTests : IDisposable { [Fact] public void Defensive() @@ -673,4 +675,335 @@ public void GetsClassNameWithSegmentsToSkipForClassNames() // validate that we get a valid class name Assert.Equal("json", responseClassName); } + [Fact] + public void SinglePathParametersAreDeduplicated() + { + var userSchema = new OpenApiSchema + { + Type = "object", + Properties = new Dictionary { + { + "id", new OpenApiSchema { + Type = "string" + } + }, + { + "displayName", new OpenApiSchema { + Type = "string" + } + } + }, + Reference = new OpenApiReference + { + Id = "#/components/schemas/microsoft.graph.user" + }, + UnresolvedReference = false + }; + var document = new OpenApiDocument + { + Paths = new OpenApiPaths + { + ["users/{foo}/careerAdvisor/{id}"] = new OpenApiPathItem + { + Parameters = { + new OpenApiParameter { + Name = "foo", + In = ParameterLocation.Path, + Required = true, + Schema = new OpenApiSchema { + Type = "string" + } + }, + }, + Operations = { + [OperationType.Get] = new OpenApiOperation + { + Responses = new OpenApiResponses { + ["200"] = new OpenApiResponse + { + Content = { + ["application/json"] = new OpenApiMediaType + { + Schema = userSchema + } + } + } + } + } + } + }, + ["users/{id}/careerAdvisor"] = new OpenApiPathItem + { + Parameters = { + new OpenApiParameter { + Name = "id", + In = ParameterLocation.Path, + Required = true, + Schema = new OpenApiSchema { + Type = "string" + } + }, + }, + Operations = { + [OperationType.Get] = new OpenApiOperation + { + Responses = new OpenApiResponses { + ["200"] = new OpenApiResponse + { + Content = { + ["application/json"] = new OpenApiMediaType + { + Schema = userSchema + } + } + } + } + } + } + }, + ["users/{user-id}/manager"] = new OpenApiPathItem + { + Operations = { + [OperationType.Get] = new OpenApiOperation + { + Parameters = { + new OpenApiParameter { + Name = "user-id", + In = ParameterLocation.Path, + Required = true, + Schema = new OpenApiSchema { + Type = "string" + } + }, + }, + Responses = new OpenApiResponses { + ["200"] = new OpenApiResponse + { + Content = { + ["application/json"] = new OpenApiMediaType + { + Schema = userSchema + } + } + } + } + } + } + }, + }, + Components = new OpenApiComponents + { + Schemas = new Dictionary { + { + "microsoft.graph.user", userSchema + } + } + } + }; + var mockLogger = new CountLogger(); + var builder = new KiotaBuilder(mockLogger, new GenerationConfiguration { ClientClassName = "Graph", ApiRootUrl = "https://localhost" }, _httpClient); + var node = builder.CreateUriSpace(document); + node.MergeIndexNodesAtSameLevel(mockLogger); + var usersCollectionIndexNode = GetChildNodeByPath(node, "users/{foo-id}"); + Assert.NotNull(usersCollectionIndexNode); + Assert.Equal("{+baseurl}/users/{foo%2Did}", usersCollectionIndexNode.GetUrlTemplate()); + + var managerNode = GetChildNodeByPath(node, "users/{foo-id}/manager"); + Assert.NotNull(managerNode); + Assert.Equal("{+baseurl}/users/{foo%2Did}/manager", managerNode.GetUrlTemplate()); + + var careerAdvisorNode = GetChildNodeByPath(node, "users/{foo-id}/careerAdvisor"); + Assert.NotNull(careerAdvisorNode); + Assert.Equal("{+baseurl}/users/{foo%2Did}/careerAdvisor", careerAdvisorNode.GetUrlTemplate()); + + var careerAdvisorIndexNode = GetChildNodeByPath(node, "users/{foo-id}/careerAdvisor/{id}"); + Assert.NotNull(careerAdvisorIndexNode); + Assert.Equal("{+baseurl}/users/{foo%2Did}/careerAdvisor/{id}", careerAdvisorIndexNode.GetUrlTemplate()); + var pathItem = careerAdvisorIndexNode.PathItems[Constants.DefaultOpenApiLabel]; + Assert.NotNull(pathItem); + var parameter = pathItem.Parameters.FirstOrDefault(static p => p.Name == "foo-id"); + Assert.NotNull(parameter); + } + [Fact] + public void SinglePathParametersAreDeduplicatedAndOrderIsRespected() + { + var ownerSchema = new OpenApiSchema + { + Type = "object", + Properties = new Dictionary { + { + "id", new OpenApiSchema { + Type = "string" + } + } + }, + Reference = new OpenApiReference + { + Id = "#/components/schemas/owner" + }, + UnresolvedReference = false + }; + var repoSchema = new OpenApiSchema + { + Type = "object", + Properties = new Dictionary { + { + "id", new OpenApiSchema { + Type = "string" + } + } + }, + Reference = new OpenApiReference + { + Id = "#/components/schemas/repo" + }, + UnresolvedReference = false + }; + var document = new OpenApiDocument + { + Paths = new OpenApiPaths + { + ["/repos/{owner}/{repo}"] = new OpenApiPathItem + { + Operations = { + [OperationType.Get] = new OpenApiOperation + { + Responses = new OpenApiResponses { + ["200"] = new OpenApiResponse + { + Content = { + ["application/json"] = new OpenApiMediaType + { + Schema = repoSchema + } + } + } + } + } + } + }, + ["/repos/{template_owner}/{template_repo}/generate"] = new OpenApiPathItem + { + Operations = { + [OperationType.Get] = new OpenApiOperation + { + Responses = new OpenApiResponses { + ["200"] = new OpenApiResponse + { + Content = { + ["application/json"] = new OpenApiMediaType + { + Schema = repoSchema + } + } + } + } + } + } + } + }, + Components = new OpenApiComponents + { + Schemas = new Dictionary { + {"owner", ownerSchema}, + {"repo", repoSchema} + } + } + }; + var mockLogger = new CountLogger(); + var builder = new KiotaBuilder(mockLogger, new GenerationConfiguration { ClientClassName = "GitHub", ApiRootUrl = "https://localhost" }, _httpClient); + var node = builder.CreateUriSpace(document); + node.MergeIndexNodesAtSameLevel(mockLogger); + + // Expected + var resultNode = GetChildNodeByPath(node, "repos/{owner-id}/{repo-id}/generate"); + Assert.NotNull(resultNode); + Assert.Equal("\\repos\\{owner-id}\\{repo-id}\\generate", resultNode.Path); + Assert.Equal("{+baseurl}/repos/{owner%2Did}/{repo%2Did}/generate", resultNode.GetUrlTemplate()); + } + [Fact] + public void repro4085() + { + var document = new OpenApiDocument + { + Paths = new OpenApiPaths + { + ["/path/{thingId}/abc/{second}"] = new OpenApiPathItem + { + Operations = { + [OperationType.Get] = new OpenApiOperation + { + Responses = new OpenApiResponses { + ["200"] = new OpenApiResponse + { + Content = { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema { + Type = "string" + } + } + } + } + } + } + } + }, + ["/path/{differentThingId}/def/{second}"] = new OpenApiPathItem + { + Operations = { + [OperationType.Get] = new OpenApiOperation + { + Responses = new OpenApiResponses { + ["200"] = new OpenApiResponse + { + Content = { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema { + Type = "string" + } + } + } + } + } + } + } + } + } + }; + var mockLogger = new CountLogger(); + var builder = new KiotaBuilder(mockLogger, new GenerationConfiguration { ClientClassName = "GitHub", ApiRootUrl = "https://localhost" }, _httpClient); + var node = builder.CreateUriSpace(document); + node.MergeIndexNodesAtSameLevel(mockLogger); + + // Expected + var resultNode = GetChildNodeByPath(node, "path"); + Assert.NotNull(resultNode); + Assert.Equal("\\path", resultNode.Path); + + Assert.Null(GetChildNodeByPath(resultNode, "{thingId}")); + Assert.Null(GetChildNodeByPath(resultNode, "{differentThingId}")); + + var differentThingId = GetChildNodeByPath(resultNode, "{differentThingId-id}"); + Assert.Equal("\\path\\{differentThingId-id}", differentThingId.Path); + Assert.Equal("{+baseurl}/path/{differentThingId%2Did}", differentThingId.GetUrlTemplate()); + } + private static OpenApiUrlTreeNode GetChildNodeByPath(OpenApiUrlTreeNode node, string path) + { + var pathSegments = path.Split('/'); + if (pathSegments.Length == 0) + return null; + if (pathSegments.Length == 1 && node.Children.TryGetValue(pathSegments[0], out var result)) + return result; + if (node.Children.TryGetValue(pathSegments[0], out var currentNode)) + return GetChildNodeByPath(currentNode, string.Join('/', pathSegments.Skip(1))); + return null; + } + public void Dispose() + { + _httpClient.Dispose(); + GC.SuppressFinalize(this); + } + private static readonly HttpClient _httpClient = new(); } diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index cc0f7850af..473b2775b4 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -6472,126 +6472,6 @@ await File.WriteAllTextAsync(tempFilePath, @$"openapi: 3.0.1 Assert.NotNull(inlineType); } [Fact] - public void SinglePathParametersAreDeduplicated() - { - var userSchema = new OpenApiSchema - { - Type = "object", - Properties = new Dictionary { - { - "id", new OpenApiSchema { - Type = "string" - } - }, - { - "displayName", new OpenApiSchema { - Type = "string" - } - } - }, - Reference = new OpenApiReference - { - Id = "#/components/schemas/microsoft.graph.user" - }, - UnresolvedReference = false - }; - var document = new OpenApiDocument - { - Paths = new OpenApiPaths - { - ["users/{foo}/careerAdvisor/{id}"] = new OpenApiPathItem - { - Operations = { - [OperationType.Get] = new OpenApiOperation - { - Responses = new OpenApiResponses { - ["200"] = new OpenApiResponse - { - Content = { - ["application/json"] = new OpenApiMediaType - { - Schema = userSchema - } - } - } - } - } - } - }, - ["users/{id}/careerAdvisor"] = new OpenApiPathItem - { - Operations = { - [OperationType.Get] = new OpenApiOperation - { - Responses = new OpenApiResponses { - ["200"] = new OpenApiResponse - { - Content = { - ["application/json"] = new OpenApiMediaType - { - Schema = userSchema - } - } - } - } - } - } - }, - ["users/{user-id}/manager"] = new OpenApiPathItem - { - Operations = { - [OperationType.Get] = new OpenApiOperation - { - Responses = new OpenApiResponses { - ["200"] = new OpenApiResponse - { - Content = { - ["application/json"] = new OpenApiMediaType - { - Schema = userSchema - } - } - } - } - } - } - }, - }, - Components = new OpenApiComponents - { - Schemas = new Dictionary { - { - "microsoft.graph.user", userSchema - } - } - } - }; - var mockLogger = new CountLogger(); - var builder = new KiotaBuilder(mockLogger, new GenerationConfiguration { ClientClassName = "Graph", ApiRootUrl = "https://localhost" }, _httpClient); - var node = builder.CreateUriSpace(document); - var codeModel = builder.CreateSourceModel(node); - var usersRB = codeModel.FindNamespaceByName("ApiSdk.users").FindChildByName("UsersRequestBuilder", false); - Assert.NotNull(usersRB); - var usersIndexer = usersRB.Indexer; - Assert.NotNull(usersIndexer); - Assert.Equal("users%2Did", usersIndexer.IndexParameter.SerializationName); - var managerRB = codeModel.FindNamespaceByName("ApiSdk.users.item.manager").FindChildByName("ManagerRequestBuilder", false); - Assert.NotNull(managerRB); - var managerUrlTemplate = managerRB.FindChildByName("UrlTemplate", false); - Assert.NotNull(managerUrlTemplate); - Assert.Equal("{+baseurl}/users/{users%2Did}/manager", managerUrlTemplate.DefaultValue.Trim('"')); - var careerAdvisorRB = codeModel.FindNamespaceByName("ApiSdk.users.item.careerAdvisor").FindChildByName("CareerAdvisorRequestBuilder", false); - Assert.NotNull(careerAdvisorRB); - var careerAdvisorUrlTemplate = careerAdvisorRB.FindChildByName("UrlTemplate", false); - Assert.NotNull(careerAdvisorUrlTemplate); - Assert.Equal("{+baseurl}/users/{users%2Did}/careerAdvisor", careerAdvisorUrlTemplate.DefaultValue.Trim('"')); - var careerAdvisorItemRB = codeModel.FindNamespaceByName("ApiSdk.users.item.careerAdvisor.item").FindChildByName("CareerAdvisorItemRequestBuilder", false); - Assert.NotNull(careerAdvisorItemRB); - var careerAdvisorItemUrlTemplate = careerAdvisorItemRB.FindChildByName("UrlTemplate", false); - Assert.NotNull(careerAdvisorItemUrlTemplate); - Assert.Equal("{+baseurl}/users/{users%2Did}/careerAdvisor/{id}", careerAdvisorItemUrlTemplate.DefaultValue.Trim('"')); - } - [Fact] public void AddReservedPathParameterSymbol() { var userSchema = new OpenApiSchema