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