From 5b442ce30a19579def60b7e142a80385e2e5457a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Le=20Douget?= Date: Fri, 24 Nov 2023 16:39:32 +0100 Subject: [PATCH 1/7] Manage URL path parameters between segments --- src/Kiota.Builder/KiotaBuilder.cs | 14 +++- .../Kiota.Builder.Tests/KiotaBuilderTests.cs | 73 ++++++++++++++----- 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index cd4c41799a..4be8026c58 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1081,9 +1081,10 @@ private CodeParameter GetIndexerParameterType(OpenApiUrlTreeNode currentNode, Op var parameterName = string.Join(OpenAPIUrlTreeNodePathSeparator, currentNode.Path.Split(OpenAPIUrlTreeNodePathSeparator, StringSplitOptions.RemoveEmptyEntries) .Skip(parentNode.Path.Count(static x => x == OpenAPIUrlTreeNodePathSeparator))) .Trim(OpenAPIUrlTreeNodePathSeparator, ForwardSlash, '{', '}'); - var parameter = currentNode.PathItems.TryGetValue(Constants.DefaultOpenApiLabel, out var pathItem) ? pathItem.Parameters + var pathItems = GetPathItems(currentNode); + var parameter = pathItems.TryGetValue(Constants.DefaultOpenApiLabel, out var pathItem) ? pathItem.Parameters .Select(static x => new { Parameter = x, IsPathParameter = true }) - .Union(currentNode.PathItems[Constants.DefaultOpenApiLabel].Operations.SelectMany(static x => x.Value.Parameters).Select(static x => new { Parameter = x, IsPathParameter = false })) + .Union(pathItems[Constants.DefaultOpenApiLabel].Operations.SelectMany(static x => x.Value.Parameters).Select(static x => new { Parameter = x, IsPathParameter = false })) .OrderBy(static x => x.IsPathParameter) .Select(static x => x.Parameter) .FirstOrDefault(x => x.Name.Equals(parameterName, StringComparison.OrdinalIgnoreCase) && x.In == ParameterLocation.Path) : @@ -1106,6 +1107,15 @@ private CodeParameter GetIndexerParameterType(OpenApiUrlTreeNode currentNode, Op }; return result; } + private Dictionary GetPathItems(OpenApiUrlTreeNode currentNode) + { + var result = currentNode.PathItems.ToDictionary(); + foreach(var child in currentNode.Children) + { + result = result.Union(GetPathItems(child.Value)).ToDictionary(); + } + return result; + } private CodeIndexer[] CreateIndexer(string childIdentifier, string childType, CodeParameter parameter, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode) { logger.LogTrace("Creating indexer {Name}", childIdentifier); diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 153be20b73..3cc72d3ac0 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -6204,6 +6204,23 @@ public async Task IndexerTypeIsAccurateAndBackwardCompatibleIndexersAreAdded() application/json: schema: $ref: '#/components/schemas/microsoft.graph.post' + /authors/{author-id}/posts/{post-id}: + get: + parameters: + - name: author-id + in: path + required: true + description: The id of the author's posts to retrieve + schema: + type: string + format: uuid + responses: + 200: + description: Success! + content: + application/json: + schema: + $ref: '#/components/schemas/microsoft.graph.post' components: schemas: microsoft.graph.post: @@ -6218,24 +6235,44 @@ public async Task IndexerTypeIsAccurateAndBackwardCompatibleIndexersAreAdded() var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document!); var codeModel = builder.CreateSourceModel(node); - var collectionRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.me.posts"); - Assert.NotNull(collectionRequestBuilderNamespace); - var collectionRequestBuilder = collectionRequestBuilderNamespace.FindChildByName("postsRequestBuilder"); - var collectionIndexer = collectionRequestBuilder.Indexer; - Assert.NotNull(collectionIndexer); - Assert.Equal("integer", collectionIndexer.IndexParameter.Type.Name); - Assert.Equal("The id of the pet to retrieve", collectionIndexer.IndexParameter.Documentation.Description, StringComparer.OrdinalIgnoreCase); - Assert.False(collectionIndexer.IndexParameter.Type.IsNullable); - Assert.False(collectionIndexer.Deprecation.IsDeprecated); - var collectionStringIndexer = collectionRequestBuilder.FindChildByName($"{collectionIndexer.Name}-string"); - Assert.NotNull(collectionStringIndexer); - Assert.Equal("string", collectionStringIndexer.IndexParameter.Type.Name); - Assert.True(collectionStringIndexer.IndexParameter.Type.IsNullable); - Assert.True(collectionStringIndexer.Deprecation.IsDeprecated); - var itemRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.me.posts.item"); - Assert.NotNull(itemRequestBuilderNamespace); - var itemRequestBuilder = itemRequestBuilderNamespace.FindChildByName("postItemRequestBuilder"); - Assert.Equal(collectionIndexer.ReturnType.Name, itemRequestBuilder.Name); + + var postsCollectionRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.me.posts"); + Assert.NotNull(postsCollectionRequestBuilderNamespace); + var postsCollectionRequestBuilder = postsCollectionRequestBuilderNamespace.FindChildByName("postsRequestBuilder"); + var postsCollectionIndexer = postsCollectionRequestBuilder.Indexer; + Assert.NotNull(postsCollectionIndexer); + Assert.Equal("integer", postsCollectionIndexer.IndexParameter.Type.Name); + Assert.Equal("The id of the pet to retrieve", postsCollectionIndexer.IndexParameter.Documentation.Description, StringComparer.OrdinalIgnoreCase); + Assert.False(postsCollectionIndexer.IndexParameter.Type.IsNullable); + Assert.False(postsCollectionIndexer.Deprecation.IsDeprecated); + var postsCollectionStringIndexer = postsCollectionRequestBuilder.FindChildByName($"{postsCollectionIndexer.Name}-string"); + Assert.NotNull(postsCollectionStringIndexer); + Assert.Equal("string", postsCollectionStringIndexer.IndexParameter.Type.Name); + Assert.True(postsCollectionStringIndexer.IndexParameter.Type.IsNullable); + Assert.True(postsCollectionStringIndexer.Deprecation.IsDeprecated); + var postsItemRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.me.posts.item"); + Assert.NotNull(postsItemRequestBuilderNamespace); + var postsItemRequestBuilder = postsItemRequestBuilderNamespace.FindChildByName("postItemRequestBuilder"); + Assert.Equal(postsCollectionIndexer.ReturnType.Name, postsItemRequestBuilder.Name); + + var authorsCollectionRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.authors"); + Assert.NotNull(authorsCollectionRequestBuilderNamespace); + var authorsCollectionRequestBuilder = authorsCollectionRequestBuilderNamespace.FindChildByName("authorsRequestBuilder"); + var authorsCollectionIndexer = authorsCollectionRequestBuilder.Indexer; + Assert.NotNull(authorsCollectionIndexer); + Assert.Equal("Guid", authorsCollectionIndexer.IndexParameter.Type.Name); + Assert.Equal("The id of the author's posts to retrieve", authorsCollectionIndexer.IndexParameter.Documentation.Description, StringComparer.OrdinalIgnoreCase); + Assert.False(authorsCollectionIndexer.IndexParameter.Type.IsNullable); + Assert.False(authorsCollectionIndexer.Deprecation.IsDeprecated); + var authorsCllectionStringIndexer = authorsCollectionRequestBuilder.FindChildByName($"{authorsCollectionIndexer.Name}-string"); + Assert.NotNull(authorsCllectionStringIndexer); + Assert.Equal("string", authorsCllectionStringIndexer.IndexParameter.Type.Name); + Assert.True(authorsCllectionStringIndexer.IndexParameter.Type.IsNullable); + Assert.True(authorsCllectionStringIndexer.Deprecation.IsDeprecated); + var authorsItemRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.authors.item"); + Assert.NotNull(authorsItemRequestBuilderNamespace); + var authorsItemRequestBuilder = authorsItemRequestBuilderNamespace.FindChildByName("authorItemRequestBuilder"); + Assert.Equal(authorsCollectionIndexer.ReturnType.Name, authorsItemRequestBuilder.Name); } [Fact] public async Task MapsBooleanEnumToBooleanType() From 9fc1ebe0cc739799e8e99c4a4923101b366749e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Le=20Douget?= Date: Mon, 27 Nov 2023 09:08:03 +0100 Subject: [PATCH 2/7] If parameter node without PathItems, get its from next child --- src/Kiota.Builder/KiotaBuilder.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 4be8026c58..142baa115f 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1109,12 +1109,21 @@ private CodeParameter GetIndexerParameterType(OpenApiUrlTreeNode currentNode, Op } private Dictionary GetPathItems(OpenApiUrlTreeNode currentNode) { - var result = currentNode.PathItems.ToDictionary(); - foreach(var child in currentNode.Children) + if(currentNode.IsParameter && currentNode.PathItems.Any()) { - result = result.Union(GetPathItems(child.Value)).ToDictionary(); + return currentNode.PathItems.ToDictionary(); } - return result; + + if (currentNode.Children.Any()) + { + return currentNode.Children + .First() + .Value + .PathItems + .ToDictionary(); + } + + return new(); } private CodeIndexer[] CreateIndexer(string childIdentifier, string childType, CodeParameter parameter, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode) { From ea4b803f73fb44cbb1a028ff30f2bf5ef7019366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Le=20Douget?= Date: Mon, 27 Nov 2023 09:20:12 +0100 Subject: [PATCH 3/7] Update unit test --- tests/Kiota.Builder.Tests/KiotaBuilderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 3cc72d3ac0..e6a6da1b47 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -6204,7 +6204,7 @@ public async Task IndexerTypeIsAccurateAndBackwardCompatibleIndexersAreAdded() application/json: schema: $ref: '#/components/schemas/microsoft.graph.post' - /authors/{author-id}/posts/{post-id}: + /authors/{author-id}/posts: get: parameters: - name: author-id From c6b17e0e41d676c0ce284d320f95bdfe04f111fc Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Nov 2023 08:56:23 -0500 Subject: [PATCH 4/7] Apply suggestions from code review --- src/Kiota.Builder/KiotaBuilder.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 142baa115f..34786309f8 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1107,23 +1107,22 @@ private CodeParameter GetIndexerParameterType(OpenApiUrlTreeNode currentNode, Op }; return result; } - private Dictionary GetPathItems(OpenApiUrlTreeNode currentNode) + private static IDictionary GetPathItems(OpenApiUrlTreeNode currentNode. bool validateIsParamterNode = true) { - if(currentNode.IsParameter && currentNode.PathItems.Any()) + if((!validateIsParameterNode || currentNode.IsParameter) && currentNode.PathItems.Any()) { - return currentNode.PathItems.ToDictionary(); + return currentNode.PathItems; } if (currentNode.Children.Any()) { return currentNode.Children - .First() - .Value - .PathItems - .ToDictionary(); + .SelectMany(static x => GetPathItems(x, false)) + .DistinctBy(static x => x.Key, StringComparer.Ordinal) + .ToDictionary(static x => x.Key, static x => x.Value, StringComparer.Ordinal); } - return new(); + return new Dictionary(); } private CodeIndexer[] CreateIndexer(string childIdentifier, string childType, CodeParameter parameter, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode) { From 90ae6cd52253d1fbc784704be513c2af997e350b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Nov 2023 09:01:19 -0500 Subject: [PATCH 5/7] - missing coma --- src/Kiota.Builder/KiotaBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 34786309f8..bfc51e0266 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1107,7 +1107,7 @@ private CodeParameter GetIndexerParameterType(OpenApiUrlTreeNode currentNode, Op }; return result; } - private static IDictionary GetPathItems(OpenApiUrlTreeNode currentNode. bool validateIsParamterNode = true) + private static IDictionary GetPathItems(OpenApiUrlTreeNode currentNode, bool validateIsParamterNode = true) { if((!validateIsParameterNode || currentNode.IsParameter) && currentNode.PathItems.Any()) { From 30a4298aa2153b7c21a7eb686b10952aa750a607 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Nov 2023 09:13:56 -0500 Subject: [PATCH 6/7] - typo fix --- src/Kiota.Builder/KiotaBuilder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index bfc51e0266..791dd0a087 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1107,9 +1107,9 @@ private CodeParameter GetIndexerParameterType(OpenApiUrlTreeNode currentNode, Op }; return result; } - private static IDictionary GetPathItems(OpenApiUrlTreeNode currentNode, bool validateIsParamterNode = true) + private static IDictionary GetPathItems(OpenApiUrlTreeNode currentNode, bool validateIsParameterNode = true) { - if((!validateIsParameterNode || currentNode.IsParameter) && currentNode.PathItems.Any()) + if ((!validateIsParameterNode || currentNode.IsParameter) && currentNode.PathItems.Any()) { return currentNode.PathItems; } @@ -1117,7 +1117,7 @@ private static IDictionary GetPathItems(OpenApiUrlTreeN if (currentNode.Children.Any()) { return currentNode.Children - .SelectMany(static x => GetPathItems(x, false)) + .SelectMany(static x => GetPathItems(x.Value, false)) .DistinctBy(static x => x.Key, StringComparer.Ordinal) .ToDictionary(static x => x.Key, static x => x.Value, StringComparer.Ordinal); } From 5db904bdce1a5d808a0720bdb5d7536ca777ead2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Le=20Douget?= Date: Thu, 7 Dec 2023 15:47:10 +0100 Subject: [PATCH 7/7] Changelog Update UT to check with a URL path with multiple depths Return empty immutable dictionary --- CHANGELOG.md | 1 + src/Kiota.Builder/KiotaBuilder.cs | 3 +- .../Kiota.Builder.Tests/KiotaBuilderTests.cs | 36 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04bbf4a2d3..a41b03697e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug where a property named constructor would make generation fail. [#3756](https://github.com/microsoft/kiota/issues/3756) - Removed reflection for Java enum deserialization. [microsoft/kiota-java#843](https://github.com/microsoft/kiota-java/pull/843) - Added support for AnyOf arrays. [#3786](https://github.com/microsoft/kiota/pull/3792) +- Fixed a bug where path parameter type was not correctly detected during generation. [#3791](https://github.com/microsoft/kiota/issues/3791) ## [1.8.2] - 2023-11-08 diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 791dd0a087..797ee28c73 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; using System.IO; @@ -1122,7 +1123,7 @@ private static IDictionary GetPathItems(OpenApiUrlTreeN .ToDictionary(static x => x.Key, static x => x.Value, StringComparer.Ordinal); } - return new Dictionary(); + return ImmutableDictionary.Empty; } private CodeIndexer[] CreateIndexer(string childIdentifier, string childType, CodeParameter parameter, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode) { diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index e6a6da1b47..f66d6aaf0e 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -6221,6 +6221,23 @@ public async Task IndexerTypeIsAccurateAndBackwardCompatibleIndexersAreAdded() application/json: schema: $ref: '#/components/schemas/microsoft.graph.post' + /actors/{actor-id}/foo/baz: + get: + parameters: + - name: actor-id + in: path + required: true + description: The id of the actor + schema: + type: string + format: uuid + responses: + 200: + description: Success! + content: + application/json: + schema: + $ref: '#/components/schemas/microsoft.graph.post' components: schemas: microsoft.graph.post: @@ -6273,6 +6290,25 @@ public async Task IndexerTypeIsAccurateAndBackwardCompatibleIndexersAreAdded() Assert.NotNull(authorsItemRequestBuilderNamespace); var authorsItemRequestBuilder = authorsItemRequestBuilderNamespace.FindChildByName("authorItemRequestBuilder"); Assert.Equal(authorsCollectionIndexer.ReturnType.Name, authorsItemRequestBuilder.Name); + + var actorsCollectionRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.actors"); + Assert.NotNull(actorsCollectionRequestBuilderNamespace); + var actorsCollectionRequestBuilder = actorsCollectionRequestBuilderNamespace.FindChildByName("actorsRequestBuilder"); + var actorsCollectionIndexer = actorsCollectionRequestBuilder.Indexer; + Assert.NotNull(actorsCollectionIndexer); + Assert.Equal("Guid", actorsCollectionIndexer.IndexParameter.Type.Name); + Assert.Equal("The id of the actor", actorsCollectionIndexer.IndexParameter.Documentation.Description, StringComparer.OrdinalIgnoreCase); + Assert.False(actorsCollectionIndexer.IndexParameter.Type.IsNullable); + Assert.False(actorsCollectionIndexer.Deprecation.IsDeprecated); + var actorsCllectionStringIndexer = actorsCollectionRequestBuilder.FindChildByName($"{actorsCollectionIndexer.Name}-string"); + Assert.NotNull(actorsCllectionStringIndexer); + Assert.Equal("string", actorsCllectionStringIndexer.IndexParameter.Type.Name); + Assert.True(actorsCllectionStringIndexer.IndexParameter.Type.IsNullable); + Assert.True(actorsCllectionStringIndexer.Deprecation.IsDeprecated); + var actorsItemRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.actors.item"); + Assert.NotNull(actorsItemRequestBuilderNamespace); + var actorsItemRequestBuilder = actorsItemRequestBuilderNamespace.FindChildByName("actorItemRequestBuilder"); + Assert.Equal(actorsCollectionIndexer.ReturnType.Name, actorsItemRequestBuilder.Name); } [Fact] public async Task MapsBooleanEnumToBooleanType()