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

Add support for generating multiple files per Refit interface #502

Merged
merged 6 commits into from
Oct 2, 2024
Merged
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
8 changes: 4 additions & 4 deletions src/Refitter.Core/FilenameConstants.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
namespace Refitter.Core;

public static class FilenameConstants
public static class TypenameConstants
{
public const string RefitInterfaces = "RefitInterfaces.cs";
public const string Contracts = "Contracts.cs";
public const string DependencyInjection = "DependencyInjection.cs";
public const string RefitInterfaces = "RefitInterfaces";
public const string Contracts = "Contracts";
public const string DependencyInjection = "DependencyInjection";
}
5 changes: 4 additions & 1 deletion src/Refitter.Core/GeneratedCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
namespace Refitter.Core;

[ExcludeFromCodeCoverage]
public record GeneratedCode(string Filename, string Content);
public record GeneratedCode(string TypeName, string Content)
{
public string Filename { get; } = $"{TypeName}.cs";
}
4 changes: 2 additions & 2 deletions src/Refitter.Core/IRefitInterfaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ namespace Refitter.Core;

internal interface IRefitInterfaceGenerator
{
RefitGeneratedCode GenerateCode();
}
IEnumerable<GeneratedCode> GenerateCode();
}
80 changes: 59 additions & 21 deletions src/Refitter.Core/RefitGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Text;
using System.Text;
using System.Text.RegularExpressions;
using NSwag;

Expand Down Expand Up @@ -141,17 +141,20 @@ public string Generate()
_ => new RefitInterfaceGenerator(settings, document, generator, docGenerator),
};

var generatedCode = GenerateClient(interfaceGenerator);
var refitInterfaces = GenerateClient(interfaceGenerator);
var interfaceNames = refitInterfaces.Select(c => c.TypeName).ToArray();
var refitInterfacesCode = string.Join("", refitInterfaces.Select(c => c.Content));
var title = settings.Naming.UseOpenApiTitle && !string.IsNullOrWhiteSpace(document.Info?.Title)
? document.Info!.Title.Sanitize()
: settings.Naming.InterfaceName;
return new StringBuilder()
.AppendLine(settings.GenerateClients ? generatedCode.SourceCode : string.Empty)
.AppendLine(settings.GenerateClients ? refitInterfacesCode : string.Empty)
.AppendLine()
.AppendLine(settings.GenerateContracts ? contracts : string.Empty)
.AppendLine(settings.ApizrSettings != null
? ApizrRegistrationGenerator.Generate(settings, generatedCode.InterfaceNames, title)
: DependencyInjectionGenerator.Generate(settings, generatedCode.InterfaceNames))
.AppendLine(
settings.ApizrSettings != null
? ApizrRegistrationGenerator.Generate(settings, interfaceNames, title)
: DependencyInjectionGenerator.Generate(settings, interfaceNames))
.ToString()
.TrimEnd();
}
Expand All @@ -174,12 +177,15 @@ public GeneratorOutput GenerateMultipleFiles()

var generatedFiles = new List<GeneratedCode>();

var interfaces = GenerateClient(interfaceGenerator);
generatedFiles.Add(new GeneratedCode(FilenameConstants.RefitInterfaces, interfaces.SourceCode));
var refitInterfaces = GenerateClient(interfaceGenerator);
generatedFiles.AddRange(refitInterfaces);

if (settings.GenerateContracts)
{
generatedFiles.Add(new GeneratedCode(FilenameConstants.Contracts, contracts));
generatedFiles.Add(
new GeneratedCode(
TypenameConstants.Contracts,
contracts));
}

if (settings.DependencyInjectionSettings is not null || settings.ApizrSettings is not null)
Expand All @@ -188,13 +194,17 @@ public GeneratorOutput GenerateMultipleFiles()
? document.Info!.Title.Sanitize()
: settings.Naming.InterfaceName;

var interfaceNames = refitInterfaces.Select(c => c.TypeName).ToArray();
var configurationCode = settings.ApizrSettings != null
? ApizrRegistrationGenerator.Generate(settings, interfaces.InterfaceNames, title)
: DependencyInjectionGenerator.Generate(settings, interfaces.InterfaceNames);
? ApizrRegistrationGenerator.Generate(settings, interfaceNames, title)
: DependencyInjectionGenerator.Generate(settings, interfaceNames);

if (!string.IsNullOrWhiteSpace(configurationCode))
{
generatedFiles.Add(new GeneratedCode(FilenameConstants.DependencyInjection, configurationCode));
generatedFiles.Add(
new GeneratedCode(
TypenameConstants.DependencyInjection,
configurationCode));
}
}

Expand All @@ -206,7 +216,7 @@ public GeneratorOutput GenerateMultipleFiles()
/// </summary>
/// <param name="interfaceGenerator">The interface generator used to generate the client code.</param>
/// <returns>The generated client code as a string.</returns>
private RefitGeneratedCode GenerateClient(IRefitInterfaceGenerator interfaceGenerator)
private IReadOnlyCollection<GeneratedCode> GenerateClient(IRefitInterfaceGenerator interfaceGenerator)
{
var code = new StringBuilder();
GenerateAutoGeneratedHeader(code);
Expand All @@ -229,14 +239,42 @@ private RefitGeneratedCode GenerateClient(IRefitInterfaceGenerator interfaceGene
code.AppendLine();

var refitInterfaces = interfaceGenerator.GenerateCode();
code.AppendLine($$"""
namespace {{settings.Namespace}}
{
{{refitInterfaces}}
}
""");

return new RefitGeneratedCode(code.ToString(), refitInterfaces.InterfaceNames);
var generatedCodes = refitInterfaces as GeneratedCode[] ?? refitInterfaces.ToArray();

if (settings.GenerateMultipleFiles)
{
for (int i = 0; i < generatedCodes.Length; i++)
{
generatedCodes[i] = generatedCodes[i] with
{
Content = code +
$$"""
namespace {{settings.Namespace}}
{
{{generatedCodes[i].Content}}
}
"""
};
}
return generatedCodes;
}

code.AppendLine($"namespace {settings.Namespace}");
code.AppendLine("{");

foreach (var generatedCode in generatedCodes)
{
code.AppendLine(generatedCode.Content);
}

code.Append("}");

return new[] { new GeneratedCode(generatedCodes.First().TypeName, code.ToString()) }
.Union(
generatedCodes
.Skip(1)
.Select(c => new GeneratedCode(c.TypeName, string.Empty)))
.ToArray();
}

/// <summary>
Expand Down
11 changes: 6 additions & 5 deletions src/Refitter.Core/RefitInterfaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,18 @@ internal RefitInterfaceGenerator(
generator.BaseSettings.OperationNameGenerator = new OperationNameGenerator(document, settings);
}

public virtual RefitGeneratedCode GenerateCode()
public virtual IEnumerable<GeneratedCode> GenerateCode()
{
return new RefitGeneratedCode(
var interfaceDeclaration = GenerateInterfaceDeclaration(out var interfaceName);
yield return new GeneratedCode(
interfaceName,
$$"""
{{GenerateInterfaceDeclaration(out var interfaceName)}}
{{interfaceDeclaration}}
{{Separator}}{
{{GenerateInterfaceBody(out var dynamicQuerystringParameters)}}
{{Separator}}}
{{dynamicQuerystringParameters}}
""",
interfaceName);
""");
}

private string GenerateInterfaceBody(out string? dynamicQuerystringParameters)
Expand Down
42 changes: 26 additions & 16 deletions src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Text;
using System.Text;
using NSwag;

namespace Refitter.Core;
Expand All @@ -16,7 +16,7 @@ internal RefitMultipleInterfaceByTagGenerator(
{
}

public override RefitGeneratedCode GenerateCode()
public override IEnumerable<GeneratedCode> GenerateCode()
{
var ungroupedTitle = settings.Naming.UseOpenApiTitle
? IdentifierUtils.Sanitize(document.Info?.Title ?? "ApiClient")
Expand All @@ -28,8 +28,7 @@ public override RefitGeneratedCode GenerateCode()
.GroupBy(x => GetGroupName(x.Operation.Value, ungroupedTitle), (k, v) => new { Key = k, Combined = v });

Dictionary<string, StringBuilder> interfacesByGroup = new();
var interfaceNames = new List<string>();
var dynamicQuerystringParametersCodeBuilder = new StringBuilder();
Dictionary<string, string> interfacesNamesByGroup = new();

foreach (var kv in byGroup)
{
Expand All @@ -53,21 +52,31 @@ public override RefitGeneratedCode GenerateCode()
this.docGenerator.AppendInterfaceDocumentation(operation, sb);

interfaceName = GetInterfaceName(kv.Key);
interfaceNames.Add(interfaceName);
sb.AppendLine($$"""
{{GenerateInterfaceDeclaration(interfaceName)}}
{{Separator}}{
""");

interfacesNamesByGroup[kv.Key] = interfaceName;
}

var operationName = GetOperationName(interfaceName, op.PathItem.Key, operations.Key, operation);
var dynamicQuerystringParameterType = operationName + "QueryParams";
var operationModel = generator.CreateOperationModel(operation);
var parameters = ParameterExtractor.GetParameters(operationModel, operation, settings, dynamicQuerystringParameterType, out var operationDynamicQuerystringParameters).ToList();
var parameters = ParameterExtractor
.GetParameters(
operationModel,
operation,
settings,
dynamicQuerystringParameterType,
out var operationDynamicQuerystringParameters)
.ToList();

var hasDynamicQuerystringParameter = !string.IsNullOrWhiteSpace(operationDynamicQuerystringParameters);
if (hasDynamicQuerystringParameter)
dynamicQuerystringParametersCodeBuilder.AppendLine(operationDynamicQuerystringParameters);
yield return new GeneratedCode(
dynamicQuerystringParameterType,
operationDynamicQuerystringParameters!);

var parametersString = string.Join(", ", parameters);
var hasApizrRequestOptionsParameter = settings.ApizrSettings?.WithRequestOptions == true;
Expand Down Expand Up @@ -97,23 +106,24 @@ public override RefitGeneratedCode GenerateCode()
}
}

var code = new StringBuilder();
foreach (var value in interfacesByGroup.Select(kv => kv.Value))
foreach (var keyValuePair in interfacesByGroup)
{
var code = new StringBuilder();
var key = keyValuePair.Key;
var value = keyValuePair.Value;

while (char.IsWhiteSpace(value[value.Length - 1]))
{
value.Length--;
}

code.AppendLine(value.ToString());
code.AppendLine($"{Separator}}}");
code.AppendLine();
}

if (dynamicQuerystringParametersCodeBuilder.Length > 0)
code.AppendLine(dynamicQuerystringParametersCodeBuilder.ToString());

return new RefitGeneratedCode(code.ToString(), interfaceNames.ToArray());
yield return new GeneratedCode(
interfacesNamesByGroup[key],
code.ToString());
}
}

private string GetGroupName(OpenApiOperation operation, string ungroupedTitle)
Expand Down Expand Up @@ -158,4 +168,4 @@ private string GenerateInterfaceDeclaration(string name)
{Separator}{modifier} partial interface {name}
""";
}
}
}
26 changes: 10 additions & 16 deletions src/Refitter.Core/RefitMultipleInterfaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,8 @@ internal RefitMultipleInterfaceGenerator(
{
}

public override RefitGeneratedCode GenerateCode()
public override IEnumerable<GeneratedCode> GenerateCode()
{
var code = new StringBuilder();
var interfaceNames = new List<string>();
var dynamicQuerystringParametersCodeBuilder = new StringBuilder();

foreach (var kv in document.Paths)
{
foreach (var operations in kv.Value)
Expand All @@ -36,11 +32,11 @@ public override RefitGeneratedCode GenerateCode()
var returnType = GetTypeName(operation);
var verb = operations.Key.CapitalizeFirstCharacter();
var methodName = settings.OperationNameTemplate ?? "Execute";
var code = new StringBuilder();

this.docGenerator.AppendInterfaceDocumentation(operation, code);

var interfaceName = GetInterfaceName(kv, verb, operation);
interfaceNames.Add(interfaceName);
code.AppendLine($$"""
{{GenerateInterfaceDeclaration(interfaceName)}}
{{Separator}}{
Expand All @@ -52,7 +48,9 @@ public override RefitGeneratedCode GenerateCode()

var hasDynamicQuerystringParameter = !string.IsNullOrWhiteSpace(methodDynamicQuerystringParameters);
if (hasDynamicQuerystringParameter)
dynamicQuerystringParametersCodeBuilder.AppendLine(methodDynamicQuerystringParameters);
yield return new GeneratedCode(
dynamicQuerystringParameterType,
methodDynamicQuerystringParameters!);

var parametersString = string.Join(", ", parameters);
var hasApizrRequestOptionsParameter = settings.ApizrSettings?.WithRequestOptions == true;
Expand All @@ -64,8 +62,7 @@ public override RefitGeneratedCode GenerateCode()

code.AppendLine($"{Separator}{Separator}[{verb}(\"{kv.Key}\")]")
.AppendLine($"{Separator}{Separator}{returnType} {methodName}({parametersString});")
.AppendLine($"{Separator}}}")
.AppendLine();
.AppendLine($"{Separator}}}");

if (parametersString.Contains("?") && settings is { OptionalParameters: true, ApizrSettings: not null })
{
Expand All @@ -80,13 +77,10 @@ public override RefitGeneratedCode GenerateCode()
.AppendLine($"{Separator}{Separator}{returnType} {methodName}({parametersString});")
.AppendLine();
}

yield return new GeneratedCode(interfaceName, code.ToString());
}
}

if (dynamicQuerystringParametersCodeBuilder.Length > 0)
code.AppendLine(dynamicQuerystringParametersCodeBuilder.ToString());

return new RefitGeneratedCode(code.ToString(), interfaceNames.ToArray());
}

private string GetInterfaceName(
Expand All @@ -113,4 +107,4 @@ private string GenerateInterfaceDeclaration(string name)
{Separator}{modifier} partial interface {name}
""";
}
}
}
1 change: 1 addition & 0 deletions src/Refitter.Core/Refitter.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<PackageReference Include="NSwag.CodeGeneration.CSharp" Version="14.1.0" />
<PackageReference Include="NSwag.Core.Yaml" Version="14.1.0" />
<PackageReference Include="OasReader" Version="1.6.16.15" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,6 @@ public partial interface ISwaggerPetstore

}


//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,13 +451,11 @@ public partial interface IDeleteUserEndpoint
Task Execute(string username);
}


}





#nullable enable
namespace Refitter.Tests.AdditionalFiles.ByEndpoint
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,5 +371,4 @@ public partial interface IUserApi
Task DeleteUser(string username);
}


}
Loading
Loading