diff --git a/src/Refitter.Core/OperationNameGenerator.cs b/src/Refitter.Core/OperationNameGenerator.cs index 02dd5036..1256d1b0 100644 --- a/src/Refitter.Core/OperationNameGenerator.cs +++ b/src/Refitter.Core/OperationNameGenerator.cs @@ -35,7 +35,8 @@ public string GetOperationName( .CapitalizeFirstCharacter() .ConvertKebabCaseToPascalCase() .ConvertRouteToCamelCase() - .ConvertSpacesToPascalCase(); + .ConvertSpacesToPascalCase() + .ConvertColonsToPascalCase(); public bool CheckForDuplicateOperationIds( OpenApiDocument document) diff --git a/src/Refitter.Core/RefitGenerator.cs b/src/Refitter.Core/RefitGenerator.cs index 1f429bf8..5ce9d2a1 100644 --- a/src/Refitter.Core/RefitGenerator.cs +++ b/src/Refitter.Core/RefitGenerator.cs @@ -17,7 +17,7 @@ public class RefitGenerator(RefitGeneratorSettings settings, OpenApiDocument doc /// A new instance of the class. public static async Task CreateAsync(RefitGeneratorSettings settings) { - var openApiDocument = await OpenApiDocumentFactory.CreateAsync(settings); + var openApiDocument = await GetOpenApiDocument(settings); ProcessTagFilters(openApiDocument, settings.IncludeTags); ProcessPathFilters(openApiDocument, settings.IncludePathMatches); @@ -27,6 +27,36 @@ public static async Task CreateAsync(RefitGeneratorSettings sett return new RefitGenerator(settings, openApiDocument); } + private static async Task GetOpenApiDocument(RefitGeneratorSettings settings) + { + var specialCharacters = new[] + { + ":" + }; + + return specialCharacters.Aggregate( + await OpenApiDocumentFactory.CreateAsync(settings), + SanitizePath); + } + + private static OpenApiDocument SanitizePath( + OpenApiDocument openApiDocument, + string stringToRemove) + { + var paths = openApiDocument.Paths.Keys + .Where(pathKey => pathKey.Contains(stringToRemove)) + .ToArray(); + + foreach (var path in paths) + { + var value = openApiDocument.Paths[path]; + openApiDocument.Paths.Remove(path); + openApiDocument.Paths.Add(path.Replace(stringToRemove, string.Empty), value); + } + + return openApiDocument; + } + private static void ProcessContractFilter(OpenApiDocument openApiDocument, bool removeUnusedSchema, string[] includeSchemaMatches) { if (!removeUnusedSchema) diff --git a/src/Refitter.Core/StringCasingExtensions.cs b/src/Refitter.Core/StringCasingExtensions.cs index 32ccb7c7..e2804c83 100644 --- a/src/Refitter.Core/StringCasingExtensions.cs +++ b/src/Refitter.Core/StringCasingExtensions.cs @@ -38,6 +38,9 @@ public static string ConvertRouteToCamelCase(this string str) public static string CapitalizeFirstCharacter(this string str) { + if (string.IsNullOrEmpty(str)) + return str; + return str.Substring(0, 1).ToUpperInvariant() + str.Substring(1, str.Length - 1); } @@ -52,4 +55,15 @@ public static string ConvertSpacesToPascalCase(this string str) return string.Join(string.Empty, parts); } + + public static string ConvertColonsToPascalCase(this string str) + { + var parts = str.Split(':'); + for (var i = 0; i < parts.Length; i++) + { + parts[i] = parts[i].CapitalizeFirstCharacter(); + } + + return string.Join(string.Empty, parts); + } } \ No newline at end of file diff --git a/src/Refitter.Tests/Examples/ColonsInPathTests.cs b/src/Refitter.Tests/Examples/ColonsInPathTests.cs new file mode 100644 index 00000000..4f2f21dd --- /dev/null +++ b/src/Refitter.Tests/Examples/ColonsInPathTests.cs @@ -0,0 +1,90 @@ +using FluentAssertions; +using FluentAssertions.Execution; +using Refitter.Core; +using Refitter.Tests.Build; +using Xunit; + +namespace Refitter.Tests.Examples; + +public class ColonsInPathTests +{ + private const string OpenApiSpec = @" +swagger: '2.0' +info: + title: Reference parameters + version: v0.0.1 +paths: + '/orders/{orderId}/:orderItems/{orderItemId}': + parameters: + - $ref: '#/parameters/OrderId' + - $ref: '#/parameters/OrderItemId' + delete: + summary: Delete an order item + description: >- + This method allows to remove an order item from an order, by specifying + their ids. + responses: + '204': + description: No Content. +parameters: + OrderId: + name: orderId + in: path + description: Identifier of the order. + required: true + type: string + format: uuid + OrderItemId: + name: orderItemId + in: path + description: Identifier of the order item. + required: true + type: string + format: uuid +"; + + [Fact] + public async Task Can_Generate_Code() + { + var generateCode = await GenerateCode(); + generateCode.Should().NotBeNullOrWhiteSpace(); + } + + [Fact] + public async Task Generates_Path_Without_Colons() + { + var generateCode = await GenerateCode(); + using var scope = new AssertionScope(); + generateCode.Should().NotContain("/:"); + } + + [Fact] + public async Task Can_Build_Generated_Code() + { + var generateCode = await GenerateCode(); + BuildHelper + .BuildCSharp(generateCode) + .Should() + .BeTrue(); + } + + private static async Task GenerateCode() + { + var swaggerFile = await CreateSwaggerFile(OpenApiSpec); + var settings = new RefitGeneratorSettings { OpenApiPath = swaggerFile }; + + var sut = await RefitGenerator.CreateAsync(settings); + var generateCode = sut.Generate(); + return generateCode; + } + + private static async Task CreateSwaggerFile(string contents) + { + var filename = $"{Guid.NewGuid()}.yml"; + var folder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(folder); + var swaggerFile = Path.Combine(folder, filename); + await File.WriteAllTextAsync(swaggerFile, contents); + return swaggerFile; + } +} \ No newline at end of file diff --git a/src/Refitter.Tests/StringCasingExtensionTests.cs b/src/Refitter.Tests/StringCasingExtensionTests.cs index f8924da0..f8820a4a 100644 --- a/src/Refitter.Tests/StringCasingExtensionTests.cs +++ b/src/Refitter.Tests/StringCasingExtensionTests.cs @@ -21,6 +21,11 @@ public void CanConvertToCamelCase(string input, string expected) public void CanCaptilalizeFirstLetter(string input, string expected) => input.CapitalizeFirstCharacter().Should().Be(expected); + [Theory] + [InlineData("", "")] + public void CaptilalizeFirstLetterHandlesEmptyStrings(string input, string expected) + => input.CapitalizeFirstCharacter().Should().Be(expected); + [Theory] [InlineData("foo/bar", "fooBar")] public void CanConvertRouteToCamelCase(string input, string expected) @@ -30,4 +35,9 @@ public void CanConvertRouteToCamelCase(string input, string expected) [InlineData("foo bar", "FooBar")] public void CanConvertSpacesToPascalCase(string input, string expected) => input.ConvertSpacesToPascalCase().Should().Be(expected); + + [Theory] + [InlineData("foo:bar", "FooBar")] + public void CanConvertColonsToPascalCase(string input, string expected) + => input.ConvertColonsToPascalCase().Should().Be(expected); } \ No newline at end of file