Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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: 1 addition & 2 deletions src/WebApiDebugger/WebApiDebugger.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject>
<OpenApiEnabled>true</OpenApiEnabled>
<OpenApiEnabled>false</OpenApiEnabled>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
</ItemGroup>
<PropertyGroup>
<OpenApiEnabled>false</OpenApiEnabled>
<OpenApiDebuggingEnabled>true</OpenApiDebuggingEnabled>
<OpenApiDevelopmentMode>GenerateContract</OpenApiDevelopmentMode>
</PropertyGroup>
Expand Down
3 changes: 2 additions & 1 deletion src/WebApiDebugger/openapi-v1.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
openapi: 3.0.1
# DO NOT EDIT. This is a generated file
openapi: 3.0.4
info:
title: WebApiDebugger
version: '1.0'
Expand Down
80 changes: 75 additions & 5 deletions src/Workleap.OpenApi.MSBuild/SwaggerManager.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Microsoft.Build.Framework;

namespace Workleap.OpenApi.MSBuild;

internal sealed class SwaggerManager : ISwaggerManager
{
private const string SwaggerVersion = "9.0.4";
private const string DefaultSwaggerVersion = "9.0.4";
private const string DoNotEditComment = "# DO NOT EDIT. This is a generated file\n";
private const int MaxRetryCount = 3;
private readonly IProcessWrapper _processWrapper;
Expand All @@ -14,14 +16,28 @@ internal sealed class SwaggerManager : ISwaggerManager
private readonly string _swaggerDirectory;
private readonly string _openApiToolsDirectoryPath;
private readonly string _swaggerExecutablePath;
private readonly string _swaggerVersion;

public SwaggerManager(ILoggerWrapper loggerWrapper, IProcessWrapper processWrapper, string openApiToolsDirectoryPath, string openApiWebApiAssemblyPath)
{
this._processWrapper = processWrapper;
this._loggerWrapper = loggerWrapper;
this._openApiWebApiAssemblyPath = openApiWebApiAssemblyPath;
this._openApiToolsDirectoryPath = openApiToolsDirectoryPath;
this._swaggerDirectory = Path.Combine(openApiToolsDirectoryPath, "swagger", SwaggerVersion);

// Detect Swashbuckle version from the application's assemblies
var assemblyDirectory = Path.GetDirectoryName(openApiWebApiAssemblyPath);
if (string.IsNullOrEmpty(assemblyDirectory))
{
this._loggerWrapper.LogMessage("Could not determine assembly directory from path. Using default Swashbuckle CLI version.", MessageImportance.Low);
this._swaggerVersion = DefaultSwaggerVersion;
}
else
{
this._swaggerVersion = this.DetectSwashbuckleVersion(assemblyDirectory) ?? DefaultSwaggerVersion;
}

this._swaggerDirectory = Path.Combine(openApiToolsDirectoryPath, "swagger", this._swaggerVersion);
this._swaggerExecutablePath = Path.Combine(this._swaggerDirectory, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "swagger.exe" : "swagger");
}

Expand All @@ -48,13 +64,13 @@ public async Task InstallSwaggerCliAsync(CancellationToken cancellationToken)
return;
}

this._loggerWrapper.LogMessage($"🔧 Installing Swashbuckle.AspNetCore.Cli {SwaggerVersion}...");
this._loggerWrapper.LogMessage($"🔧 Installing Swashbuckle.AspNetCore.Cli {this._swaggerVersion}...");

for (var retryCount = 0; retryCount < MaxRetryCount; retryCount++)
{
var result = await this._processWrapper.RunProcessAsync(
"dotnet",
["tool", "update", "Swashbuckle.AspNetCore.Cli", "--ignore-failed-sources", "--tool-path", this._swaggerDirectory, "--configfile", Path.Combine(this._openApiToolsDirectoryPath, "nuget.config"), "--version", SwaggerVersion],
["tool", "update", "Swashbuckle.AspNetCore.Cli", "--ignore-failed-sources", "--tool-path", this._swaggerDirectory, "--configfile", Path.Combine(this._openApiToolsDirectoryPath, "nuget.config"), "--version", this._swaggerVersion],
cancellationToken);

var isLastRetry = retryCount == MaxRetryCount - 1;
Expand All @@ -75,7 +91,7 @@ public async Task InstallSwaggerCliAsync(CancellationToken cancellationToken)
break;
}

this._loggerWrapper.LogMessage($"✅ Swashbuckle.AspNetCore.Cli {SwaggerVersion} installed successfully.");
this._loggerWrapper.LogMessage($"✅ Swashbuckle.AspNetCore.Cli {this._swaggerVersion} installed successfully.");
}

public async Task<string> GenerateOpenApiSpecAsync(string swaggerExePath, string outputOpenApiSpecPath, string documentName, CancellationToken cancellationToken)
Expand Down Expand Up @@ -123,4 +139,58 @@ private void PrependComment(string outputOpenApiSpecPath, string comment)
this._loggerWrapper.LogWarning($"Failed to add comment to generated spec for {outputOpenApiSpecPath}.");
}
}

private string? DetectSwashbuckleVersion(string? assemblyDirectory)
{
if (string.IsNullOrEmpty(assemblyDirectory) || !Directory.Exists(assemblyDirectory))
{
this._loggerWrapper.LogMessage("Could not detect Swashbuckle version: assembly directory not found. Using default version.", MessageImportance.Low);
return null;
}

try
{
// Look for Swashbuckle.AspNetCore.SwaggerGen.dll in the assembly directory
// (Swashbuckle.AspNetCore is a metapackage, so we check for one of its actual assemblies)
var swashbuckleAssemblyPath = Path.Combine(assemblyDirectory, "Swashbuckle.AspNetCore.SwaggerGen.dll");

if (!File.Exists(swashbuckleAssemblyPath))
{
this._loggerWrapper.LogMessage("Could not detect Swashbuckle version: Swashbuckle.AspNetCore.SwaggerGen.dll not found in assembly directory. Using default version.", MessageImportance.Low);
return null;
}

// Get the assembly version using FileVersionInfo (doesn't load the assembly)
var versionInfo = FileVersionInfo.GetVersionInfo(swashbuckleAssemblyPath);
var productVersion = versionInfo.ProductVersion;

if (!string.IsNullOrEmpty(productVersion))
{
// Product version might include metadata like "9.0.6+commit", extract just the version number
var versionParts = productVersion.Split('+', '-');
var versionString = versionParts[0];

// Validate that the extracted version matches semantic versioning pattern (e.g., 9.0.4, 10.0.1)
// Pattern: major.minor.patch format, optionally followed by build number
// Requires at least 3 parts (major.minor.patch) as per semantic versioning
if (!string.IsNullOrWhiteSpace(versionString) &&
Regex.IsMatch(versionString, @"^\d{1,4}\.\d{1,4}\.\d{1,4}(\.\d{1,4})?$"))
{
this._loggerWrapper.LogMessage($"✓ Detected Swashbuckle.AspNetCore version: {versionString}", MessageImportance.Normal);
return versionString;
}

this._loggerWrapper.LogMessage($"Detected version '{versionString}' does not match expected format. Using default version.", MessageImportance.Low);
return null;
}

this._loggerWrapper.LogMessage("Could not detect Swashbuckle version from assembly metadata. Using default version.", MessageImportance.Low);
return null;
}
catch (Exception ex)
{
this._loggerWrapper.LogMessage($"Error detecting Swashbuckle version: {ex.Message}. Using default version.", MessageImportance.Low);
return null;
}
}
}