Skip to content

Commit

Permalink
Merge pull request #79 from christianhelle/fix-support-for-spaces-in-…
Browse files Browse the repository at this point in the history
…operationid

Fix support for spaces in operationid
  • Loading branch information
christianhelle authored Jun 20, 2023
2 parents 84ba232 + 4b6b907 commit b6556f9
Show file tree
Hide file tree
Showing 13 changed files with 303 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/production-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- "**/*"
- "!.github/workflows/production-tests.yml"
branches:
- "*"
- "main"
schedule:
- cron: '0 8 * * *'

Expand Down
18 changes: 16 additions & 2 deletions .github/workflows/regression-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- "**/*"
- "!.github/workflows/regression-tests.yml"
branches:
- "*"
- "main"

jobs:
template:
Expand All @@ -17,7 +17,21 @@ jobs:
format: [json, yaml]
version: [v2.0, v3.0]
os: [macos-latest, windows-latest, ubuntu-latest]
openapi: ["callback-example", "link-example", "uber", "uspto", "petstore"]
openapi: [
"api-with-examples",
"callback-example",
"link-example",
"uber",
"uspto",
"petstore",
"petstore-expanded",
"petstore-minimal",
"petstore-simple",
"petstore-with-external-docs",
"ingram-micro",
"hubspot-events",
"hubspot-webhooks"
]
uses: ./.github/workflows/template.yml
with:
format: ${{ matrix.format }}
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/smoke-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,19 @@ jobs:
version: [v2.0, v3.0]
os: [ubuntu-latest]
openapi: [
"api-with-examples",
"callback-example",
"link-example",
"uber",
"uspto",
"petstore",
"ingram-micro"
"petstore-expanded",
"petstore-minimal",
"petstore-simple",
"petstore-with-external-docs",
"ingram-micro",
"hubspot-events",
"hubspot-webhooks"
]

uses: ./.github/workflows/template.yml
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ test/ConsoleApp/**/Output.cs
test/openapi.json
test/openapi.yaml
test/*.cs
test/**/*Api-with-examples.cs
test/**/*Callback-example.cs
test/**/*Link-example.cs
test/**/*Petstore*.cs
Expand Down
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dotnet.defaultSolution": "src\\Refitter.sln"
}
6 changes: 5 additions & 1 deletion src/Refitter.Core/OperationNameGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using NSwag;
using NSwag.CodeGeneration.OperationNameGenerators;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace Refitter.Core;
Expand All @@ -16,8 +17,10 @@ public OperationNameGenerator(OpenApiDocument document)
defaultGenerator = new MultipleClientsFromFirstTagAndPathSegmentsOperationNameGenerator();
}

[ExcludeFromCodeCoverage]
public bool SupportsMultipleClients => throw new System.NotImplementedException();

[ExcludeFromCodeCoverage]
public string GetClientName(OpenApiDocument document, string path, string httpMethod, OpenApiOperation operation)
{
return defaultGenerator.GetClientName(document, path, httpMethod, operation);
Expand All @@ -32,7 +35,8 @@ public string GetOperationName(
.GetOperationName(document, path, httpMethod, operation)
.CapitalizeFirstCharacter()
.ConvertKebabCaseToPascalCase()
.ConvertRouteToCamelCase();
.ConvertRouteToCamelCase()
.ConvertSpacesToPascalCase();

public bool CheckForDuplicateOperationIds(
OpenApiDocument document)
Expand Down
11 changes: 11 additions & 0 deletions src/Refitter.Core/StringCasingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,15 @@ public static string CapitalizeFirstCharacter(this string str)
return str.Substring(0, 1).ToUpperInvariant() +
str.Substring(1, str.Length - 1);
}

public static string ConvertSpacesToPascalCase(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);
}
}
48 changes: 48 additions & 0 deletions src/Refitter.Tests/Examples/OpenApiUrlTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#if !DEBUG
using FluentAssertions;
using Refitter.Core;
using Refitter.Tests.Build;
using Xunit;

namespace Refitter.Tests.Examples;

public class OpenApiUrlTests
{
[Theory]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/api-with-examples.json")]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/callback-example.json")]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/link-example.json")]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/petstore-expanded.json")]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/uspto.json")]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/hubspot-events.json")]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/hubspot-webhooks.json")]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/ingram-micro.json")]
public async Task Can_Build_Generated_Code_From_OpenApi_v3_Url_Json(string url)
{
var settings = new RefitGeneratorSettings { OpenApiPath = url };
var sut = await RefitGenerator.CreateAsync(settings);
var generateCode = sut.Generate();
BuildHelper
.BuildCSharp(generateCode)
.Should()
.BeTrue();
}

[Theory]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/api-with-examples.yaml")]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/callback-example.yaml")]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/link-example.yaml")]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/petstore-expanded.yaml")]
[InlineData("https://raw.githubusercontent.com/christianhelle/refitter/main/test/OpenAPI/v3.0/uspto.yaml")]
public async Task Can_Build_Generated_Code_From_OpenApi_v3_Url_Yaml(string url)
{
var settings = new RefitGeneratorSettings { OpenApiPath = url };
var sut = await RefitGenerator.CreateAsync(settings);
var generateCode = sut.Generate();
BuildHelper
.BuildCSharp(generateCode)
.Should()
.BeTrue();
}
}
#endif
74 changes: 74 additions & 0 deletions src/Refitter.Tests/Examples/OperationIdWithSpacesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using FluentAssertions;
using Refitter.Core;
using Refitter.Tests.Build;
using Xunit;

namespace Refitter.Tests.Examples;

public class OperationIdWithSpacesTests
{
private const string OpenApiSpec = @"
openapi: '3.0.0'
paths:
/jobs/{job-id}:
get:
tags:
- 'Jobs'
operationId: 'Get job details'
description: 'Get the details of the specified job.'
parameters:
- in: 'path'
name: 'job-id'
description: 'Job ID'
required: true
schema:
type: 'string'
responses:
'200':
description: 'successful operation'
";

[Fact]
public async Task Can_Generate_Code()
{
string generateCode = await GenerateCode();
generateCode.Should().NotBeNullOrWhiteSpace();
}

[Fact]
public async Task Replaces_KababCase_Parameters_With_PascalCase()
{
string generateCode = await GenerateCode();
generateCode.Should().Contain("string job_id");
}

[Fact]
public async Task Can_Build_Generated_Code()
{
string generateCode = await GenerateCode();
BuildHelper
.BuildCSharp(generateCode)
.Should()
.BeTrue();
}

private static async Task<string> 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<string> 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;
}
}
98 changes: 98 additions & 0 deletions src/Refitter.Tests/Examples/PoorOperationIdsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using FluentAssertions;
using Refitter.Core;
using Refitter.Tests.Build;
using Xunit;

namespace Refitter.Tests.Examples;

public class PoorOperationIdsTests
{
private const string OpenApiSpec = @"
{
""openapi"" : ""3.0.1"",
""paths"" : {
""/api/v1/{id}/foo"" : {
""get"" : {
""tags"" : [ ""foo"" ],
""operationId"" : ""get-/api/v1/{id}/foo_getAll"",
""parameters"" : [ {
""name"" : ""id"",
""in"" : ""path"",
""required"" : true,
""style"" : ""simple"",
""explode"" : false,
""schema"" : {
""type"" : ""integer"",
""format"" : ""int32""
}
} ],
""responses"" : {
""200"" : {
""description"" : ""successful operation""
}
}
}
},
""/api/v1/{id}/bar"" : {
""get"" : {
""tags"" : [ ""bar"" ],
""operationId"" : ""get-/api/v1/{id}/bar_getAll"",
""parameters"" : [ {
""name"" : ""id"",
""in"" : ""path"",
""required"" : true,
""style"" : ""simple"",
""explode"" : false,
""schema"" : {
""type"" : ""integer"",
""format"" : ""int32""
}
} ],
""responses"" : {
""200"" : {
""description"" : ""successful operation""
}
}
}
}
}
}
";

[Fact]
public async Task Can_Generate_Code()
{
string generateCode = await GenerateCode();
generateCode.Should().NotBeNullOrWhiteSpace();
}

[Fact]
public async Task Can_Build_Generated_Code()
{
string generateCode = await GenerateCode();
BuildHelper
.BuildCSharp(generateCode)
.Should()
.BeTrue();
}

private static async Task<string> GenerateCode()
{
var swaggerFile = await CreateSwaggerFile(OpenApiSpec);
var foo = new RefitGeneratorSettings { OpenApiPath = swaggerFile };

var sut = await RefitGenerator.CreateAsync(foo);
var generateCode = sut.Generate();
return generateCode;
}

private static async Task<string> 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;
}
}
14 changes: 12 additions & 2 deletions src/Refitter.Tests/StringCasingExtensionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,24 @@ public class StringCasingExtensionTests
[InlineData("some-string", "SomeString")]
public void CanConvertToPascalCase(string input, string expected)
=> input.ConvertKebabCaseToPascalCase().Should().Be(expected);

[Theory]
[InlineData("some-string", "someString")]
public void CanConvertToCamelCase(string input, string expected)
=> input.ConvertKebabCaseToCamelCase().Should().Be(expected);

[Theory]
[InlineData("abcd", "Abcd")]
public void CanCaptilalizeFirstLetter(string input, string expected)
=> input.CapitalizeFirstCharacter().Should().Be(expected);

[Theory]
[InlineData("foo/bar", "fooBar")]
public void CanConvertRouteToCamelCase(string input, string expected)
=> input.ConvertRouteToCamelCase().Should().Be(expected);

[Theory]
[InlineData("foo bar", "FooBar")]
public void CanConvertSpacesToPascalCase(string input, string expected)
=> input.ConvertSpacesToPascalCase().Should().Be(expected);
}
Loading

0 comments on commit b6556f9

Please sign in to comment.