diff --git a/CHANGELOG.md b/CHANGELOG.md
index d29ee7d31b..5c58b0dd0e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed a bug where TypeScript deserialization would fail on Uppercase properties.[#4479](https://github.com/microsoft/kiota/issues/4479)
- Changed URI template generation to reuse templates when required templates are absent across operations.
+- Fixed path deduplication logic to avoid double `Id` suffixes in indexer names in scenarios where the `Id` suffix is already present.[#4519](https://github.com/microsoft/kiota/issues/4519)
- Updated reserved name providers for Java and Php so that "object" can be escaped.
- Fixes request builder disambiguation when child nodes had different prefixes for different paths with same parameters.[#4448](https://github.com/microsoft/kiota/issues/4448)
- Do not generate CS8603 warnings when enabling backing store in CSharp generation.
diff --git a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs
index c90136b87f..06dc242496 100644
--- a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs
+++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs
@@ -320,7 +320,7 @@ internal static void MergeIndexNodesAtSameLevel(this OpenApiUrlTreeNode node, IL
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}}";
+ var newSegmentParameterName = oldSegmentName.EndsWith("-id", StringComparison.OrdinalIgnoreCase) ? oldSegmentName : $"{{{oldSegmentName.TrimSuffix("id", StringComparison.OrdinalIgnoreCase)}-id}}";
indexNode.Value.Path = indexNode.Value.Path.Replace(indexNode.Key, newSegmentParameterName, StringComparison.OrdinalIgnoreCase);
indexNode.Value.AddDeduplicatedSegment(newSegmentParameterName);
node.Children.Add(newSegmentParameterName, indexNode.Value);
diff --git a/src/Kiota.Builder/Extensions/StringExtensions.cs b/src/Kiota.Builder/Extensions/StringExtensions.cs
index 44738cec93..d220b073f1 100644
--- a/src/Kiota.Builder/Extensions/StringExtensions.cs
+++ b/src/Kiota.Builder/Extensions/StringExtensions.cs
@@ -290,4 +290,7 @@ public static string CleanupXMLString(this string? original)
///
public static bool EqualsIgnoreCase(this string? a, string? b)
=> string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
+
+ public static string TrimSuffix(this string s, string suffix, StringComparison stringComparison = StringComparison.Ordinal) =>
+ !string.IsNullOrEmpty(s) && !string.IsNullOrEmpty(suffix) && s.EndsWith(suffix, stringComparison) ? s[..^suffix.Length] : s;
}
diff --git a/src/Kiota.Builder/Writers/Go/StringExtensions.cs b/src/Kiota.Builder/Writers/Go/StringExtensions.cs
index d56a749cab..cf62649870 100644
--- a/src/Kiota.Builder/Writers/Go/StringExtensions.cs
+++ b/src/Kiota.Builder/Writers/Go/StringExtensions.cs
@@ -10,6 +10,4 @@ public static string TrimCollectionAndPointerSymbols(this string s) =>
public static string TrimPackageReference(this string s) =>
!string.IsNullOrEmpty(s) && s.Contains('.', StringComparison.InvariantCultureIgnoreCase) ? s.Split('.').Last() : s;
- public static string TrimSuffix(this string s, string suffix) =>
- !string.IsNullOrEmpty(s) && suffix != null && s.EndsWith(suffix, StringComparison.Ordinal) ? s[..^suffix.Length] : s;
}
diff --git a/tests/Kiota.Builder.Tests/Extensions/OpenApiUrlTreeNodeExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/OpenApiUrlTreeNodeExtensionsTests.cs
index 99fa6e6662..e3ce0e266e 100644
--- a/tests/Kiota.Builder.Tests/Extensions/OpenApiUrlTreeNodeExtensionsTests.cs
+++ b/tests/Kiota.Builder.Tests/Extensions/OpenApiUrlTreeNodeExtensionsTests.cs
@@ -1068,9 +1068,9 @@ public void repro4085()
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());
+ var differentThingId = GetChildNodeByPath(resultNode, "{differentThing-id}");
+ Assert.Equal("\\path\\{differentThing-id}", differentThingId.Path);
+ Assert.Equal("{+baseurl}/path/{differentThing%2Did}", differentThingId.GetUrlTemplate());
}
[Theory]
diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
index 58fcb12a22..dd25c286ed 100644
--- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
+++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs
@@ -904,7 +904,7 @@ public async Task TrimsInheritanceUnusedModelsWithUnion()
Assert.NotNull(modelsNS.FindChildByName("EducationUser", false));
Assert.Null(modelsNS.FindChildByName("AuditEvent", false));
}
- private static async Task GetDocumentStream(string document)
+ internal static async Task GetDocumentStream(string document)
{
var ms = new MemoryStream();
await using var tw = new StreamWriter(ms, Encoding.UTF8, leaveOpen: true);
diff --git a/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs
index 9857df54d6..170c31dafc 100644
--- a/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/GoLanguageRefinerTests.cs
@@ -1,10 +1,13 @@
using System;
+using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Threading.Tasks;
using Kiota.Builder.CodeDOM;
using Kiota.Builder.Configuration;
using Kiota.Builder.Refiners;
-
+using Microsoft.Extensions.Logging;
+using Moq;
using Xunit;
namespace Kiota.Builder.Tests.Refiners;
@@ -439,6 +442,86 @@ public async Task SupportsTypeSpecificOverrideIndexers()
Assert.NotNull(model.Methods.SingleOrDefault(static x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility) && x.Name.Equals("ByIdInteger") && x.OriginalIndexer != null && x.OriginalIndexer.IndexParameter.Type.Name.Equals("Integer", StringComparison.OrdinalIgnoreCase)));
Assert.NotNull(model.Methods.SingleOrDefault(static x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility) && x.Name.Equals("ById") && x.OriginalIndexer != null && x.OriginalIndexer.IndexParameter.Type.Name.Equals("string", StringComparison.OrdinalIgnoreCase)));
}
+
+ [Fact]
+ public async Task ValidatesNamingOfRequestBuilderDoesNotRepeatIdCharacter()
+ {
+ var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());
+ await using var fs = await KiotaBuilderTests.GetDocumentStream(@"openapi: 3.0.1
+info:
+ title: OData Service for namespace microsoft.graph
+ description: This OData service is located at https://graph.microsoft.com/v1.0
+ version: 1.0.1
+servers:
+ - url: https://graph.microsoft.com/v1.0
+paths:
+ /groups/{id}:
+ get:
+ summary: Get Group by Id
+ operationId: groups_GetById
+ parameters:
+ - name: id
+ in: path
+ description: The id of the group
+ required : true
+ schema :
+ type: string
+ format: uuid
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/microsoft.graph.directoryObject'
+ /groups/{groupId}/member/{id}:
+ get:
+ summary: Get member by Id
+ operationId: member_GetById
+ parameters:
+ - name: groupId
+ in: path
+ description: The id of the group
+ required : true
+ schema :
+ type: string
+ format: uuid
+ - name: id
+ in: path
+ description: The id of the member
+ required : true
+ schema :
+ type: string
+ format: uuid
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/microsoft.graph.directoryObject'
+components:
+ schemas:
+ microsoft.graph.directoryObject:
+ title: directoryObject
+ type: object
+ properties:
+ deletedDateTime:
+ type: string
+ pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$'
+ format: date-time
+ nullable: true");
+ var mockLogger = new Mock>();
+ var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, new HttpClient());
+ var document = await builder.CreateOpenApiDocumentAsync(fs);
+ var node = builder.CreateUriSpace(document);
+ var codeModel = builder.CreateSourceModel(node);
+ await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.Go }, codeModel);
+ var requestBuilder = codeModel.FindChildByName("groupsRequestBuilder");
+ var indexerMethods = requestBuilder.Methods.Where(static method =>
+ method.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility)).ToArray();
+ Assert.Equal(2, indexerMethods.Length);
+ Assert.Equal("ByGroupId", indexerMethods[0].Name);
+ Assert.Equal("ByGroupIdGuid", indexerMethods[1].Name);
+ }
[Fact]
public async Task AddsExceptionInheritanceOnErrorClasses()
{