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
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using ApiClientCodeGen.Tests.Common.Infrastructure;
using Rapicgen.CLI.Commands;
using Rapicgen.Core.Generators.NSwag;
using Rapicgen.Core.Options.NSwag;
using FluentAssertions;
using Rapicgen.CLI.Commands.CSharp;
Expand All @@ -15,13 +13,11 @@ public void Create_Should_Return_NotNull(
NSwagCodeGeneratorFactory sut,
string swaggerFile,
string defaultNamespace,
INSwagOptions options,
IOpenApiDocumentFactory documentFactory)
INSwagOptions options)
=> sut.Create(
swaggerFile,
defaultNamespace,
options,
documentFactory)
options)
.Should()
.NotBeNull();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Rapicgen.Core.Generators;
using Rapicgen.Core.Generators.NSwag;
using Rapicgen.Core.Installer;
using Rapicgen.Core.Options.NSwag;

namespace Rapicgen.CLI.Commands.CSharp
Expand All @@ -8,20 +9,31 @@ public interface INSwagCodeGeneratorFactory
{
ICodeGenerator Create(string swaggerFile,
string defaultNamespace,
INSwagOptions options,
IOpenApiDocumentFactory documentFactory);
INSwagOptions options);
}

public class NSwagCodeGeneratorFactory : INSwagCodeGeneratorFactory
{
private readonly IProcessLauncher processLauncher;
private readonly IDependencyInstaller dependencyInstaller;

public NSwagCodeGeneratorFactory(
IProcessLauncher processLauncher,
IDependencyInstaller dependencyInstaller)
{
this.processLauncher = processLauncher;
this.dependencyInstaller = dependencyInstaller;
}

public ICodeGenerator Create(
string swaggerFile,
string defaultNamespace,
INSwagOptions options,
IOpenApiDocumentFactory documentFactory)
INSwagOptions options)
=> new NSwagCSharpCodeGenerator(
swaggerFile,
documentFactory,
new NSwagCodeGeneratorSettingsFactory(defaultNamespace, options));
defaultNamespace,
processLauncher,
dependencyInstaller,
options);
}
}
9 changes: 2 additions & 7 deletions src/CLI/ApiClientCodeGen.CLI/Commands/CSharp/NswagCommand.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using NJsonSchema.CodeGeneration.CSharp;
using Rapicgen.CLI.Commands;
using Rapicgen.Core;
using Rapicgen.Core.Generators;
using Rapicgen.Core.Generators.NSwag;
using Rapicgen.Core.Installer;
using Rapicgen.Core.Logging;
using Rapicgen.Core.Options.NSwag;
using Spectre.Console.Cli;
Expand Down Expand Up @@ -59,18 +59,14 @@ Set this to FALSE to use the filename (default: TRUE)")]

public class NSwagCommand : CodeGeneratorCommand<NSwagCommandSettings>
{
private readonly IOpenApiDocumentFactory openApiDocumentFactory;
private readonly INSwagCodeGeneratorFactory codeGeneratorFactory;

public NSwagCommand(
IConsoleOutput console,
IProgressReporter? progressReporter,
IOpenApiDocumentFactory openApiDocumentFactory,
INSwagCodeGeneratorFactory codeGeneratorFactory)
: base(console, progressReporter)
{
this.openApiDocumentFactory = openApiDocumentFactory ??
throw new ArgumentNullException(nameof(openApiDocumentFactory));
this.codeGeneratorFactory =
codeGeneratorFactory ?? throw new ArgumentNullException(nameof(codeGeneratorFactory));
}
Expand All @@ -79,7 +75,6 @@ public override ICodeGenerator CreateGenerator(NSwagCommandSettings settings)
=> codeGeneratorFactory.Create(
settings.SwaggerFile,
settings.DefaultNamespace,
settings,
openApiDocumentFactory);
settings);
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
using System.Threading.Tasks;
using ApiClientCodeGen.Tests.Common;
using ApiClientCodeGen.Tests.Common;
using Rapicgen.Core;
using Rapicgen.Core.Generators;
using Rapicgen.Core.Generators.NSwag;
using Rapicgen.Core.Installer;
using Rapicgen.Core.Options.NSwag;
using FluentAssertions;
using Moq;
using NSwag;
using NSwag.CodeGeneration.CSharp;
using Xunit;

namespace ApiClientCodeGen.Core.Tests.Generators.NSwag
{
public class NSwagCSharpCodeGeneratorTests : TestWithResources
{
private readonly Mock<IProgressReporter> progressMock = new Mock<IProgressReporter>();
private readonly Mock<IOpenApiDocumentFactory> documentFactoryMock = new Mock<IOpenApiDocumentFactory>();
private readonly Mock<INSwagCodeGeneratorSettingsFactory> settingsMock = new Mock<INSwagCodeGeneratorSettingsFactory>();
private OpenApiDocument document;
private readonly Mock<IProcessLauncher> processLauncherMock = new Mock<IProcessLauncher>();
private readonly Mock<IDependencyInstaller> dependencyInstallerMock = new Mock<IDependencyInstaller>();
private readonly Mock<INSwagOptions> optionsMock = new Mock<INSwagOptions>();
private string code;

protected override async Task OnInitializeAsync()
protected override void OnInitialize()
{
document = await OpenApiDocument.FromFileAsync(SwaggerJsonFilename);
documentFactoryMock.Setup(c => c.GetDocumentAsync(It.IsAny<string>()))
.ReturnsAsync(document);

settingsMock.Setup(c => c.GetGeneratorSettings(It.IsAny<OpenApiDocument>()))
.Returns(new CSharpClientGeneratorSettings());
optionsMock.Setup(c => c.ClassStyle).Returns(CSharpClassStyle.Poco);
optionsMock.Setup(c => c.UseDocumentTitle).Returns(true);
optionsMock.Setup(c => c.InjectHttpClient).Returns(true);
optionsMock.Setup(c => c.GenerateClientInterfaces).Returns(true);
optionsMock.Setup(c => c.GenerateDtoTypes).Returns(true);
optionsMock.Setup(c => c.UseBaseUrl).Returns(false);
optionsMock.Setup(c => c.ParameterDateTimeFormat).Returns("s");

var sut = new NSwagCSharpCodeGenerator(
SwaggerJsonFilename,
documentFactoryMock.Object,
settingsMock.Object);
"GeneratedCode",
processLauncherMock.Object,
dependencyInstallerMock.Object,
optionsMock.Object);

code = sut.GenerateCode(progressMock.Object);
}
Expand All @@ -41,22 +44,25 @@ public void Updates_Progress()
c => c.Progress(
It.IsAny<uint>(),
It.IsAny<uint>()),
Times.Exactly(4));
Times.AtLeast(3));

[Fact]
public void Gets_Document_From_Factory()
=> documentFactoryMock.Verify(
c => c.GetDocumentAsync(SwaggerJsonFilename),
public void Installs_NSwag_Dependency()
=> dependencyInstallerMock.Verify(
c => c.InstallNSwag(),
Times.Once);

[Fact]
public void Gets_GeneratorSettings()
=> settingsMock.Verify(
c => c.GetGeneratorSettings(document),
public void Launches_NSwag_Process()
=> processLauncherMock.Verify(
c => c.Start(
It.Is<string>(s => s == "nswag"),
It.IsAny<string>(),
It.IsAny<string>()),
Times.Once);

[Fact]
public void Generated_Code()
=> code.Should().NotBeNullOrWhiteSpace();
=> code.Should().NotBeNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,25 @@ public void InstallAutoRest_Invokes_Npm(
}

[Theory, AutoMoqData]
public void InstallNSwag_Invokes_Npm(
[Frozen] INpmInstaller npm,
public void InstallNSwag_When_NSwag_Not_Installed_Invokes_ProcessLauncher(
[Frozen] IProcessLauncher processLauncher,
DependencyInstaller sut)
{
var mock = Mock.Get(processLauncher);
mock.Setup(
c => c.Start(
"nswag",
"version",
It.IsAny<Action<string>>(),
It.IsAny<Action<string>>(),
default))
.Throws(new Win32Exception());
sut.InstallNSwag();
Mock.Get(npm)
.Verify(c => c.InstallNpmPackage("nswag"));
mock.Verify(
c => c.Start(
It.IsAny<string>(),
"tool install --global NSwag.ConsoleCore --version 14.4.0",
null));
}

[Theory, AutoMoqData]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Rapicgen.Core.Options.NSwag;
using FluentAssertions;
using NJsonSchema.CodeGeneration.CSharp;

namespace ApiClientCodeGen.Core.Tests.Options
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Rapicgen.Core.Options.NSwagStudio;
using Rapicgen.Core.Options.NSwag;
using Rapicgen.Core.Options.NSwagStudio;
using FluentAssertions;
using NJsonSchema.CodeGeneration.CSharp;

namespace ApiClientCodeGen.Core.Tests.Options
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,110 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System;
using System.IO;
using Rapicgen.Core.Installer;
using Rapicgen.Core.Logging;
using NSwag.CodeGeneration.CSharp;
using Rapicgen.Core.Options.NSwag;

namespace Rapicgen.Core.Generators.NSwag
{
public class NSwagCSharpCodeGenerator : ICodeGenerator
{
private readonly IOpenApiDocumentFactory documentFactory;
private readonly INSwagCodeGeneratorSettingsFactory generatorSettingsFactory;
private readonly string swaggerFile;
private readonly string defaultNamespace;
private readonly IProcessLauncher processLauncher;
private readonly IDependencyInstaller dependencyInstaller;
private readonly INSwagOptions options;

private const string Command = "nswag";

public NSwagCSharpCodeGenerator(
string swaggerFile,
IOpenApiDocumentFactory documentFactory,
INSwagCodeGeneratorSettingsFactory generatorSettingsFactory)
string defaultNamespace,
IProcessLauncher processLauncher,
IDependencyInstaller dependencyInstaller,
INSwagOptions options)
{
this.swaggerFile = swaggerFile;
this.documentFactory = documentFactory ?? throw new ArgumentNullException(nameof(documentFactory));
this.generatorSettingsFactory = generatorSettingsFactory ??
throw new ArgumentNullException(nameof(generatorSettingsFactory));
this.swaggerFile = swaggerFile ?? throw new ArgumentNullException(nameof(swaggerFile));
this.defaultNamespace = defaultNamespace ?? throw new ArgumentNullException(nameof(defaultNamespace));
this.processLauncher = processLauncher ?? throw new ArgumentNullException(nameof(processLauncher));
this.dependencyInstaller = dependencyInstaller ?? throw new ArgumentNullException(nameof(dependencyInstaller));
this.options = options ?? throw new ArgumentNullException(nameof(options));
}

public string GenerateCode(IProgressReporter? pGenerateProgress)
{
try
{
using var context = new DependencyContext("NSwag");
var code = OnGenerateCode(pGenerateProgress);
context.Succeeded();
return GeneratedCode.PrefixAutogeneratedCodeHeader(code, "NSwag", "v14.4.0");
}
finally
pGenerateProgress?.Progress(10);
dependencyInstaller.InstallNSwag();

pGenerateProgress?.Progress(30);
var outputPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".cs");
var workingDirectory = Path.GetDirectoryName(swaggerFile);
var className = GenerateClassName(swaggerFile);

pGenerateProgress?.Progress(40);
var arguments = BuildNSwagArguments(outputPath, className);

using var context = new DependencyContext("NSwag", $"{Command} {arguments}");
processLauncher.Start(Command, arguments, workingDirectory);
context.Succeeded();

pGenerateProgress?.Progress(80);

string generatedCode = string.Empty;
if (File.Exists(outputPath))
{
pGenerateProgress?.Progress(90);
generatedCode = File.ReadAllText(outputPath);
File.Delete(outputPath);
}

pGenerateProgress?.Progress(100);
return GeneratedCode.PrefixAutogeneratedCodeHeader(generatedCode, "NSwag", "v14.4.0");
}

[SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "This is code is called from an old pre-TPL interface")]
private string OnGenerateCode(IProgressReporter? pGenerateProgress)
private string BuildNSwagArguments(string outputPath, string className)
{
pGenerateProgress?.Progress(10);
var document = documentFactory.GetDocumentAsync(swaggerFile).GetAwaiter().GetResult();
pGenerateProgress?.Progress(20);
var settings = generatorSettingsFactory.GetGeneratorSettings(document);
pGenerateProgress?.Progress(50);
var generator = new CSharpClientGenerator(document, settings);
return generator.GenerateFile();
var classStyle = options.ClassStyle.ToString();

var args = $"openapi2csclient " +
$"/input:\"{swaggerFile}\" " +
$"/output:\"{outputPath}\" " +
$"/namespace:{defaultNamespace} " +
$"/ClassName:{className} " +
$"/InjectHttpClient:{options.InjectHttpClient.ToString().ToLowerInvariant()} " +
$"/GenerateClientInterfaces:{options.GenerateClientInterfaces.ToString().ToLowerInvariant()} " +
$"/GenerateDtoTypes:{options.GenerateDtoTypes.ToString().ToLowerInvariant()} " +
$"/UseBaseUrl:{options.UseBaseUrl.ToString().ToLowerInvariant()} " +
$"/ClassStyle:{classStyle} " +
$"/ParameterDateTimeFormat:\"{options.ParameterDateTimeFormat}\"";

return args;
}

private string GenerateClassName(string filePath)
{
var fileInfo = new FileInfo(filePath);
if (options.UseDocumentTitle)
{
// When using document title, we let NSwag determine the class name
// But we need to provide a default - we'll use the filename as fallback
var name = fileInfo.Name
.Replace(".json", string.Empty)
.Replace(".yaml", string.Empty)
.Replace(".yml", string.Empty)
.Replace(".", string.Empty)
.Replace("-", string.Empty)
.Replace(" ", string.Empty);
return string.IsNullOrWhiteSpace(name) ? "ApiClient" : $"{name}Client";
}

var fileName = fileInfo.Name
.Replace(".json", string.Empty)
.Replace(".yaml", string.Empty)
.Replace(".yml", string.Empty)
.Replace(".", string.Empty)
.Replace("-", string.Empty)
.Replace(" ", string.Empty);

return string.IsNullOrWhiteSpace(fileName) ? "ApiClient" : fileName;
}
}
}
Loading
Loading