Repository: microsoft/kiota (csharp)

Issue:
C# - Wrong type for position parameter in RequestBuilder
Hello,

When I generate a C# client with Kiota CLI version 1.8.2, result code doesn't correctly detect parameter type in url.
Type is correctly detected only if parameter is last url part (ex. `http://myurl/<id>`).

My swagger definition:
```json
{
  "openapi": "3.0.1",
  "info": {
    "title": "Api",
    "version": "1.0"
  },
  "paths": {
    "/groups/{groupId}/users": {
      "get": {
        "tags": [
          "PersonEndpoint"
        ],
        "parameters": [
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "style": "simple",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          }
        }
      }
    },
    "/users/{userId}": {
      "get": {
        "tags": [
          "UserEndpoint"
        ],
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "style": "simple",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          }
        }
      }
    }
  },
  "components": { }
}
```

Used command line: `kiota generate -l CSharp -c MyClient -n MyNamespace -d swagger.json -o MyClient`

Generated RequestBuilder for my endpoint with parameter as last url part:
```csharp
// <auto-generated/>
using Microsoft.Kiota.Abstractions;
using MyNamespace.Users.Item;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System;
namespace MyNamespace.Users {
    /// <summary>
    /// Builds and executes requests for operations under \users
    /// </summary>
    public class UsersRequestBuilder : BaseRequestBuilder {
        /// <summary>Gets an item from the MyNamespace.users.item collection</summary>
        /// <param name="position">Unique identifier of the item</param>
        public WithUserItemRequestBuilder this[Guid position] { get {
            var urlTplParams = new Dictionary<string, object>(PathParameters);
            urlTplParams.Add("userId", position);
            return new WithUserItemRequestBuilder(urlTplParams, RequestAdapter);
        } }
        /// <summary>Gets an item from the MyNamespace.users.item collection</summary>
        /// <param name="position">Unique identifier of the item</param>
        [Obsolete("This indexer is deprecated and will be removed in the next major version. Use the one with the typed parameter instead.")]
        public WithUserItemRequestBuilder this[string position] { get {
            var urlTplParams = new Dictionary<string, object>(PathParameters);
            if (!string.IsNullOrWhiteSpace(position)) urlTplParams.Add("userId", position);
            return new WithUserItemRequestBuilder(urlTplParams, RequestAdapter);
        } }
        /// <summary>
        /// Instantiates a new UsersRequestBuilder and sets the default values.
        /// </summary>
        /// <param name="pathParameters">Path parameters for the request</param>
        /// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
        public UsersRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/users", pathParameters) {
        }
        /// <summary>
        /// Instantiates a new UsersRequestBuilder and sets the default values.
        /// </summary>
        /// <param name="rawUrl">The raw URL to use for the request builder.</param>
        /// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
        public UsersRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/users", rawUrl) {
        }
    }
}
```

Generated RequestBuilder for my endpoint with the parameter between two url part:
```csharp
// <auto-generated/>
using Microsoft.Kiota.Abstractions;
using MyNamespace.Groups.Item;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System;
namespace MyNamespace.Groups {
    /// <summary>
    /// Builds and executes requests for operations under \groups
    /// </summary>
    public class GroupsRequestBuilder : BaseRequestBuilder {
        /// <summary>Gets an item from the MyNamespace.groups.item collection</summary>
        /// <param name="position">Unique identifier of the item</param>
        public WithGroupItemRequestBuilder this[string position] { get {
            var urlTplParams = new Dictionary<string, object>(PathParameters);
            urlTplParams.Add("groupId", position);
            return new WithGroupItemRequestBuilder(urlTplParams, RequestAdapter);
        } }
        /// <summary>
        /// Instantiates a new GroupsRequestBuilder and sets the default values.
        /// </summary>
        /// <param name="pathParameters">Path parameters for the request</param>
        /// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
        public GroupsRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/groups", pathParameters) {
        }
        /// <summary>
        /// Instantiates a new GroupsRequestBuilder and sets the default values.
        /// </summary>
        /// <param name="rawUrl">The raw URL to use for the request builder.</param>
        /// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
        public GroupsRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/groups", rawUrl) {
        }
    }
}
```

I search in issues but I didn't find any tickets for this bug.

Pull request:
Fixes #3791 
When Path Parameter is between 2 segments, `PathItems` not set in Parameter node but next segment node.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b8010b9447..b839fe27a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Fixed parsing of `DateOnly` values generated in request executors [#3679](https://github.com/microsoft/kiota/issues/3679)
 - Fixes generation of default values names for go constructor functions [#3436](https://github.com/microsoft/kiota/issues/3436)
 - [Java] Removed the usage of reflection in `ApiClientBuilder` [`kiota-java#923`](https://github.com/microsoft/kiota-java/issues/923)
+- 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 376242ffe9..74a75272d5 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;
@@ -1080,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) :
@@ -1105,6 +1107,23 @@ private CodeParameter GetIndexerParameterType(OpenApiUrlTreeNode currentNode, Op
         };
         return result;
     }
+    private static IDictionary<string, OpenApiPathItem> GetPathItems(OpenApiUrlTreeNode currentNode, bool validateIsParameterNode = true)
+    {
+        if ((!validateIsParameterNode || currentNode.IsParameter) && currentNode.PathItems.Any())
+        {
+            return currentNode.PathItems;
+        }
+
+        if (currentNode.Children.Any())
+        {
+            return currentNode.Children
+                .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);
+        }
+
+        return ImmutableDictionary<string, OpenApiPathItem>.Empty;
+    }
     private CodeIndexer[] CreateIndexer(string childIdentifier, string childType, CodeParameter parameter, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode parentNode)
     {
         logger.LogTrace("Creating indexer {Name}", childIdentifier);


Test patch:
diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
index 93063066e4..102da7644b 100644
--- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
+++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
@@ -6204,6 +6204,40 @@ public async Task IndexerTypeIsAccurateAndBackwardCompatibleIndexersAreAdded()
             application/json:
               schema:
                 $ref: '#/components/schemas/microsoft.graph.post'
+  /authors/{author-id}/posts:
+    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'
+  /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:
@@ -6218,24 +6252,63 @@ 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<CodeClass>("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<CodeIndexer>($"{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<CodeClass>("postItemRequestBuilder");
-        Assert.Equal(collectionIndexer.ReturnType.Name, itemRequestBuilder.Name);
+
+        var postsCollectionRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.me.posts");
+        Assert.NotNull(postsCollectionRequestBuilderNamespace);
+        var postsCollectionRequestBuilder = postsCollectionRequestBuilderNamespace.FindChildByName<CodeClass>("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<CodeIndexer>($"{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<CodeClass>("postItemRequestBuilder");
+        Assert.Equal(postsCollectionIndexer.ReturnType.Name, postsItemRequestBuilder.Name);
+
+        var authorsCollectionRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.authors");
+        Assert.NotNull(authorsCollectionRequestBuilderNamespace);
+        var authorsCollectionRequestBuilder = authorsCollectionRequestBuilderNamespace.FindChildByName<CodeClass>("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<CodeIndexer>($"{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<CodeClass>("authorItemRequestBuilder");
+        Assert.Equal(authorsCollectionIndexer.ReturnType.Name, authorsItemRequestBuilder.Name);
+
+        var actorsCollectionRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.actors");
+        Assert.NotNull(actorsCollectionRequestBuilderNamespace);
+        var actorsCollectionRequestBuilder = actorsCollectionRequestBuilderNamespace.FindChildByName<CodeClass>("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<CodeIndexer>($"{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<CodeClass>("actorItemRequestBuilder");
+        Assert.Equal(actorsCollectionIndexer.ReturnType.Name, actorsItemRequestBuilder.Name);
     }
     [Fact]
     public async Task MapsBooleanEnumToBooleanType()