Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IDP-1271] Fallback on method name for operationId #3

Merged
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ jobs:
shell: pwsh
env:
NUGET_SOURCE: ${{ secrets.NUGET_GSOFTDEV_FEED_URL }}
NUGET_API_KEY: ${{ secrets.GSOFT_NUGET_API_KEY }}
NUGET_API_KEY: ${{ secrets.GSOFT_NUGET_API_KEY }}

4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ jobs:
- run: ./Build.ps1
shell: pwsh
env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
NUGET_API_KEY: ${{ secrets.WORKLEAP_NUGET_API_KEY }}
NUGET_SOURCE: ${{ secrets.NUGET_GSOFTDEV_FEED_URL }}
heqianwang marked this conversation as resolved.
Show resolved Hide resolved
NUGET_API_KEY: ${{ secrets.GSOFT_NUGET_API_KEY }}
37 changes: 34 additions & 3 deletions Build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,56 @@ Begin {
$ErrorActionPreference = "stop"
}



Process {
function Exec([scriptblock]$Command) {
& $Command
if ($LASTEXITCODE -ne 0) {
throw ("An error occurred while executing command: {0}" -f $Command)
}
}


function Compare-GeneratedAndExpectedFiles {
param(
[Parameter(Mandatory=$true)]
[string]$generatedFilePath,

[Parameter(Mandatory=$true)]
[string]$expectedFilePath
)

# Compare the generated file with the expected file
$generatedFileContent = Get-Content -Path $generatedFilePath
$expectedFileContent = Get-Content -Path $expectedFilePath
$diff = Compare-Object -ReferenceObject $generatedFileContent -DifferenceObject $expectedFileContent

if ($diff) {
$diff | Format-Table
Write-Error "The generated file does not match the expected file."
exit 1
} else {
Write-Host "The generated file matches the expected file."
}
}

$workingDir = Join-Path $PSScriptRoot "src"
$outputDir = Join-Path $PSScriptRoot ".output"
$nupkgsPath = Join-Path $outputDir "*.nupkg"

$projectPath = Join-Path $workingDir "tests/WebApi.OpenAPI.SystemTest"
$generatedFilePath = Join-Path $projectPath "openapi-v1.yaml"
$expectedFilePath = Join-Path $workingDir "tests/expected-openapi-document.yaml"


try {
Push-Location $workingDir
Remove-Item $outputDir -Force -Recurse -ErrorAction SilentlyContinue

Exec { & dotnet clean -c Release }
Exec { & dotnet build -c Release }
Exec { & dotnet test -c Release --no-build --results-directory "$outputDir" --no-restore -l "trx" -l "console;verbosity=detailed" }
Exec { & Compare-GeneratedAndExpectedFiles -generatedFilePath $generatedFilePath -expectedFilePath $expectedFilePath }
Exec { & dotnet pack -c Release --no-build -o "$outputDir" }

if (($null -ne $env:NUGET_SOURCE ) -and ($null -ne $env:NUGET_API_KEY)) {
Expand All @@ -32,4 +63,4 @@ Process {
finally {
Pop-Location
}
}
}
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,29 @@
[![nuget](https://img.shields.io/nuget/v/Workleap.Extensions.OpenAPI.svg?logo=nuget)](https://www.nuget.org/packages/Workleap.Extensions.OpenAPI/)
[![build](https://img.shields.io/github/actions/workflow/status/gsoft-inc/wl-extensions-openapi/publish.yml?logo=github&branch=main)](https://github.com/gsoft-inc/wl-extensions-openapi/actions/workflows/publish.yml)

The Workleap.Extensions.OpenAPI library is designed to help generate better OpenApi document with less effort.
heqianwang marked this conversation as resolved.
Show resolved Hide resolved

## Value proposition and features overview

The library offers an opinionated configuration of OpenAPI document generation and SwaggerUI.

As such, we provide the following features:

- Display OperationId in SwaggerUI
- (Optional) Fallback to use controller name as OperationId when there is no OperationId explicitly defined for the endpoint.

## Getting started

TODO
Install the package Workleap.Extensions.OpenAPI in your .NET API project. Then you may use the following method to register the required service. Here is a code snippet on how to register this and to enable the operationId fallback feature in your application.

```
public void ConfigureServices(IServiceCollection services)
{
// [...]
services.AddOpenApi().FallbackOnMethodNameForOperationId();
// [...]
}
```

## Building, releasing and versioning

Expand Down
5 changes: 4 additions & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PackageReference Include="Workleap.DotNet.CodingStandards" Version="0.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<PackageReference Include="GitVersion.MsBuild" Version="5.12.0" Condition=" '$(Configuration)' == 'Release' ">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Workleap.Extensions.OpenAPI.OperationId;

namespace Workleap.Extensions.OpenAPI.Tests.OperationId;

public class FallbackOperationIdToMethodNameFilterTests
{
[Theory]
[InlineData("GetData", "GetData")]
[InlineData("GetDataAsync", "GetData")]
[InlineData("GetDataasync", "GetData")]
heqianwang marked this conversation as resolved.
Show resolved Hide resolved
public void Given_Method_Name_When_Cleanup_Then_Clean_Name(string methodName, string expectedOutput)
{
// When
var result = FallbackOperationIdToMethodNameFilter.CleanupName(methodName);

// Then
Assert.Equal(expectedOutput, result);
}
}
10 changes: 0 additions & 10 deletions src/Workleap.Extensions.OpenAPI.Tests/UnitTest1.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
heqianwang marked this conversation as resolved.
Show resolved Hide resolved
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<SignAssembly>true</SignAssembly>
Expand All @@ -13,10 +13,6 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Workleap.DotNet.CodingStandards" Version="0.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
17 changes: 16 additions & 1 deletion src/Workleap.Extensions.OpenAPI.sln
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "files", "files", "{3B3625CC-919B-4216-9B50-BCFE297AA184}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Workleap.Extensions.OpenAPI.Tests", "Workleap.Extensions.OpenAPI.Tests\Workleap.Extensions.OpenAPI.Tests.csproj", "{2A5429CC-E179-47E7-85BB-C1E33E2AFD8A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1868781E-E97B-447A-AEB7-507739F5FA88}"
ProjectSection(SolutionItems) = preProject
tests\RunSystemTest.ps1 = tests\RunSystemTest.ps1
tests\expected-openapi-document.yaml = tests\expected-openapi-document.yaml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.OpenAPI.SystemTest", "tests\WebApi.OpenAPI.SystemTest\WebApi.OpenAPI.SystemTest.csproj", "{F7C54BE5-D538-4D2B-9B07-C77567CEB82A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -30,5 +37,13 @@ Global
{2A5429CC-E179-47E7-85BB-C1E33E2AFD8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A5429CC-E179-47E7-85BB-C1E33E2AFD8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A5429CC-E179-47E7-85BB-C1E33E2AFD8A}.Release|Any CPU.Build.0 = Release|Any CPU
{F7C54BE5-D538-4D2B-9B07-C77567CEB82A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7C54BE5-D538-4D2B-9B07-C77567CEB82A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7C54BE5-D538-4D2B-9B07-C77567CEB82A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7C54BE5-D538-4D2B-9B07-C77567CEB82A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F7C54BE5-D538-4D2B-9B07-C77567CEB82A} = {1868781E-E97B-447A-AEB7-507739F5FA88}
{2A5429CC-E179-47E7-85BB-C1E33E2AFD8A} = {1868781E-E97B-447A-AEB7-507739F5FA88}
EndGlobalSection
EndGlobal
40 changes: 40 additions & 0 deletions src/Workleap.Extensions.OpenAPI/Builder/OpenApiBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.SwaggerUI;
using Workleap.Extensions.OpenAPI.OperationId;

namespace Workleap.Extensions.OpenAPI.Builder;

/// <summary>
/// Provides methods to configure Swagger/OpenAPI opinionated settings for the application.
/// </summary>
public class OpenApiBuilder
heqianwang marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly IServiceCollection _services;

internal OpenApiBuilder(IServiceCollection services)
{
this._services = services;

this._services.AddSingleton<IConfigureOptions<SwaggerUIOptions>, DisplayOperationIdInSwaggerUiOptions>();
}

/// <summary>
/// Configures the Swagger generator to fallback on the method name as the operation ID if no explicit operation ID is specified.
/// </summary>
/// <remarks>
/// This method adds a custom operation filter to the Swagger generator.
/// </remarks>
/// <returns>
/// The same <see cref="OpenApiBuilder"/> instance so that multiple configuration calls can be chained.
/// </returns>
public OpenApiBuilder FallbackOnMethodNameForOperationId()
heqianwang marked this conversation as resolved.
Show resolved Hide resolved
{
this._services.ConfigureSwaggerGen(options =>
{
options.OperationFilter<FallbackOperationIdToMethodNameFilter>();
});

return this;
}
}
5 changes: 0 additions & 5 deletions src/Workleap.Extensions.OpenAPI/Class1.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using Workleap.Extensions.OpenAPI.Builder;

namespace Workleap.Extensions.OpenAPI;

/// <summary>
/// Provides extension methods to the <see cref="IServiceCollection"/> for configuring OpenAPI/Swagger services.
/// </summary>
public static class OpenApiServiceCollectionExtensions
{
/// <summary>
/// Configures OpenAPI/Swagger document generation and SwaggerUI.
/// </summary>
public static OpenApiBuilder AddOpenApi(this IServiceCollection services)
PrincessMadMath marked this conversation as resolved.
Show resolved Hide resolved
heqianwang marked this conversation as resolved.
Show resolved Hide resolved
{
ArgumentNullException.ThrowIfNull(services);

return new OpenApiBuilder(services);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.SwaggerUI;

namespace Workleap.Extensions.OpenAPI.OperationId;

internal class DisplayOperationIdInSwaggerUiOptions : IConfigureOptions<SwaggerUIOptions>
PrincessMadMath marked this conversation as resolved.
Show resolved Hide resolved
{
public void Configure(SwaggerUIOptions options)
{
options.DisplayOperationId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace Workleap.Extensions.OpenAPI.OperationId;

internal class FallbackOperationIdToMethodNameFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (!string.IsNullOrEmpty(operation.OperationId))
{
return;
}

// Method name for Minimal API is not the best choice for OperationId so we want to enforce explicit declaration
if (IsMinimalApi(context))
{
return;
}

operation.OperationId = CleanupName(context.MethodInfo.Name);
}

private static bool IsMinimalApi(OperationFilterContext context)
{
return !typeof(ControllerBase).IsAssignableFrom(context.MethodInfo.DeclaringType);
}

internal static string CleanupName(string methodName)
PrincessMadMath marked this conversation as resolved.
Show resolved Hide resolved
heqianwang marked this conversation as resolved.
Show resolved Hide resolved
{
return methodName.Replace("Async", string.Empty, StringComparison.InvariantCultureIgnoreCase);
heqianwang marked this conversation as resolved.
Show resolved Hide resolved
}
}
6 changes: 5 additions & 1 deletion src/Workleap.Extensions.OpenAPI/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
#nullable enable
#nullable enable
static Workleap.Extensions.OpenAPI.OpenApiServiceCollectionExtensions.AddOpenApi(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Workleap.Extensions.OpenAPI.Builder.OpenApiBuilder!
Workleap.Extensions.OpenAPI.Builder.OpenApiBuilder
Workleap.Extensions.OpenAPI.Builder.OpenApiBuilder.FallbackOnMethodNameForOperationId() -> Workleap.Extensions.OpenAPI.Builder.OpenApiBuilder!
Workleap.Extensions.OpenAPI.OpenApiServiceCollectionExtensions
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<IsPackable>true</IsPackable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
heqianwang marked this conversation as resolved.
Show resolved Hide resolved
<PackageReadmeFile>README.md</PackageReadmeFile>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../Workleap.Extensions.OpenAPI.snk</AssemblyOriginatorKeyFile>
<OpenApiGenerateDocumentsOnBuild>false</OpenApiGenerateDocumentsOnBuild>
heqianwang marked this conversation as resolved.
Show resolved Hide resolved
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Workleap.DotNet.CodingStandards" Version="0.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\README.md" Link="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
Expand Down
Loading