diff --git a/src/NSwag.CodeGeneration.Tests/CodeGenerationTests.cs b/src/NSwag.CodeGeneration.Tests/CodeGenerationTests.cs index e01227b81..b1f682aab 100644 --- a/src/NSwag.CodeGeneration.Tests/CodeGenerationTests.cs +++ b/src/NSwag.CodeGeneration.Tests/CodeGenerationTests.cs @@ -84,8 +84,8 @@ public void When_using_MultipleClientsFromFirstTagAndOperationName_then_ensure_t } [Theory(DisplayName = "Ensure expected operation name generation when using MultipleClientsFromFirstTagAndOperationName behavior")] - [InlineData("OperationId_SecondUnderscore_Test", "Test")] - [InlineData("OperationId_MultipleUnderscores_Client_Test", "Test")] + [InlineData("OperationId_SecondUnderscore_Test", "SecondUnderscore_Test")] + [InlineData("OperationId_MultipleUnderscores_Client_Test", "MultipleUnderscores_Client_Test")] [InlineData("OperationId_Test", "Test")] [InlineData("UnderscoreLast_", "UnderscoreLast_")] [InlineData("_UnderscoreFirst", "UnderscoreFirst")] @@ -111,8 +111,8 @@ public void When_using_MultipleClientsFromFirstTagAndOperationName_then_ensure_t } [Theory(DisplayName = "Ensure expected client name generation with different operationIds when using the MultipleClientsFromOperationId behavior")] - [InlineData("OperationId_SecondUnderscore_Test", "SecondUnderscore")] - [InlineData("OperationId_MultipleUnderscores_Client_Test", "Client")] + [InlineData("OperationId_SecondUnderscore_Test", "OperationId")] + [InlineData("OperationId_MultipleUnderscores_Client_Test", "OperationId")] [InlineData("OperationId_Test", "OperationId")] [InlineData("UnderscoreLast_", "UnderscoreLast")] [InlineData("_UnderscoreFirst", "")] @@ -139,5 +139,59 @@ public void When_using_MultipleClientsFromOperationId_then_ensure_that_underscor // Assert Assert.Equal(expectedClientName, clientName); } + + [Theory(DisplayName = "Ensure expected operation name generation with different operationIds when using the MultipleClientsFromOperationId behavior")] + [InlineData("OperationId_SecondUnderscore_Test", "SecondUnderscore_Test")] + [InlineData("OperationId_MultipleUnderscores_Client_Test", "MultipleUnderscores_Client_Test")] + [InlineData("OperationId_Test", "Test")] + [InlineData("UnderscoreLast_", "UnderscoreLast_")] + [InlineData("_UnderscoreFirst", "UnderscoreFirst")] + [InlineData("NoUnderscore", "NoUnderscore")] + public void When_using_MultipleClientsFromOperationId_then_ensure_that_operationname_is_correct(string operationId, string expectedOperationName) + { + // Arrange + var operation = new OpenApiOperation + { + OperationId = operationId + }; + var generator = new MultipleClientsFromOperationIdOperationNameGenerator(); + + var document = new OpenApiDocument(); + var path = string.Empty; + var httpMethod = string.Empty; + + // Act + string operationName = generator.GetOperationName(document, path, httpMethod, operation); + + // Assert + Assert.Equal(expectedOperationName, operationName); + } + + [Theory(DisplayName = "Ensure unique (client, operation) pairs when using MultipleClientsFromOperationId to avoid duplicate method names")] + [InlineData("Orders_items_get", "Products_items_get")] + [InlineData("Resource1_getSomething", "Resource2_getSomething")] + [InlineData("A_B_C", "D_B_C")] + public void When_using_MultipleClientsFromOperationId_then_unique_operationIds_produce_unique_client_operation_pairs(string operationId1, string operationId2) + { + // Arrange + var operation1 = new OpenApiOperation { OperationId = operationId1 }; + var operation2 = new OpenApiOperation { OperationId = operationId2 }; + var generator = new MultipleClientsFromOperationIdOperationNameGenerator(); + + var document = new OpenApiDocument(); + var path = string.Empty; + var httpMethod = string.Empty; + + // Act + string clientName1 = generator.GetClientName(document, path, httpMethod, operation1); + string operationName1 = generator.GetOperationName(document, path, httpMethod, operation1); + string clientName2 = generator.GetClientName(document, path, httpMethod, operation2); + string operationName2 = generator.GetOperationName(document, path, httpMethod, operation2); + + // Assert: unique operation IDs must not produce the same (client, operation) pair + Assert.False( + clientName1 == clientName2 && operationName1 == operationName2, + $"OperationIds '{operationId1}' and '{operationId2}' produced duplicate (client='{clientName1}', operation='{operationName1}') pair"); + } } } diff --git a/src/NSwag.CodeGeneration/OperationNameGenerators/MultipleClientsFromOperationIdOperationNameGenerator.cs b/src/NSwag.CodeGeneration/OperationNameGenerators/MultipleClientsFromOperationIdOperationNameGenerator.cs index b3d946e68..f74ab831e 100644 --- a/src/NSwag.CodeGeneration/OperationNameGenerators/MultipleClientsFromOperationIdOperationNameGenerator.cs +++ b/src/NSwag.CodeGeneration/OperationNameGenerators/MultipleClientsFromOperationIdOperationNameGenerator.cs @@ -81,42 +81,23 @@ public virtual string GetOperationName(OpenApiDocument document, string path, st private static ReadOnlySpan GetClientName(OpenApiOperation operation) { ReadOnlySpan operationIdSpan = operation.OperationId.AsSpan(); - const char underscoreSeparator = '_'; - int idxFirst = operationIdSpan.IndexOf(underscoreSeparator); + int idx = operationIdSpan.IndexOf('_'); - // no underscore, fast path - if (idxFirst == -1) + // no underscore or underscore is the first character + if (idx <= 0) { return []; } - int idxLast = operationIdSpan.LastIndexOf(underscoreSeparator); - - // only one underscore - if (idxFirst == idxLast) - { - // underscore is the first character - if (idxFirst == 0) - { - return []; - } - - return operationIdSpan.Slice(0, idxFirst); - } - - // backwards search for the second underscore - // e.g. OperationId_SecondUnderscore_Test => SecondUnderscore - operationIdSpan = operationIdSpan.Slice(0, idxLast); - int idxSecondLast = operationIdSpan.LastIndexOf(underscoreSeparator); - - return operationIdSpan.Slice(idxSecondLast + 1); + return operationIdSpan.Slice(0, idx); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ReadOnlySpan GetOperationName(OpenApiOperation operation) { var span = operation.OperationId.AsSpan(); - var idx = span.LastIndexOf('_'); + var idx = span.IndexOf('_'); + // No underscore, or underscore is the last character: return the full operation ID return idx != -1 && idx < span.Length - 1 ? span.Slice(idx + 1) : span;