Skip to content

Commit

Permalink
Merge pull request #286 from DJ4ddi/return-type-exclusion
Browse files Browse the repository at this point in the history
Add return type override setting.
  • Loading branch information
christianhelle authored Jan 11, 2024
2 parents e52f9f1 + 29f2775 commit 6806827
Show file tree
Hide file tree
Showing 11 changed files with 83 additions and 17 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ The following is an example `.refitter` file
"addAutoGeneratedHeader": true, // Optional. Default=true
"addAcceptHeaders": true, // Optional. Default=true
"returnIApiResponse": false, // Optional. Default=false
"responseTypeOverride": { // Optional. Default={}
"File_Upload": "IApiResponse",
"File_Download": "System.Net.Http.HttpContent"
},
"generateOperationHeaders": true, // Optional. Default=true
"typeAccessibility": "Public", // Optional. Values=Public|Internal. Default=Public
"useCancellationTokens": false, // Optional. Default=false
Expand Down Expand Up @@ -236,6 +240,7 @@ The following is an example `.refitter` file
- `addAutoGeneratedHeader` - a boolean indicating whether XML doc comments should be generated. Default is `true`
- `addAcceptHeaders` - a boolean indicating whether to add accept headers [Headers("Accept: application/json")]. Default is `true`
- `returnIApiResponse` - a boolean indicating whether to return `IApiResponse<T>` objects. Default is `false`
- `responseTypeOverride` - a dictionary with operation ids (as specified in the OpenAPI document) and a particular return type to use. The types are wrapped in a task, but otherwise unmodified (so make sure to specify or import their namespaces). Default is `{}`
- `generateOperationHeaders` - a boolean indicating whether to use operation headers in the generated methods. Default is `true`
- `typeAccessibility` - the generated type accessibility. Possible values are `Public` and `Internal`. Default is `Public`
- `useCancellationTokens` - Use cancellation tokens in the generated methods. Default is `false`
Expand Down
5 changes: 5 additions & 0 deletions docs/docfx_project/articles/refitter-file-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ The following is an example `.refitter` file
"addAutoGeneratedHeader": true, // Optional. Default=true
"addAcceptHeaders": true, // Optional. Default=true
"returnIApiResponse": false, // Optional. Default=false
"responseTypeOverride": { // Optional. Default={}
"File_Upload": "IApiResponse",
"File_Download": "System.Net.Http.HttpContent"
},
"generateOperationHeaders": true, // Optional. Default=true
"typeAccessibility": "Public", // Optional. Values=Public|Internal. Default=Public
"useCancellationTokens": false, // Optional. Default=false
Expand Down Expand Up @@ -103,6 +107,7 @@ The following is an example `.refitter` file
- `addAutoGeneratedHeader` - a boolean indicating whether XML doc comments should be generated. Default is `true`
- `addAcceptHeaders` - a boolean indicating whether to add accept headers [Headers("Accept: application/json")]. Default is `true`
- `returnIApiResponse` - a boolean indicating whether to return `IApiResponse<T>` objects. Default is `false`
- `responseTypeOverride` - a dictionary with operation ids (as specified in the OpenAPI document) and a particular return type to use. The types are wrapped in a task, but otherwise unmodified (so make sure to specify or import their namespaces). Default is `{}`
- `generateOperationHeaders` - a boolean indicating whether to use operation headers in the generated methods. Default is `true`
- `typeAccessibility` - the generated type accessibility. Possible values are `Public` and `Internal`. Default is `Public`
- `useCancellationTokens` - Use cancellation tokens in the generated methods. Default is `false`
Expand Down
20 changes: 19 additions & 1 deletion src/Refitter.Core/RefitInterfaceGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text;
using System.Text.RegularExpressions;

using NSwag;
using NSwag.CodeGeneration.CSharp.Models;
Expand Down Expand Up @@ -61,7 +62,7 @@ private string GenerateInterfaceBody()
var parameters = ParameterExtractor.GetParameters(operationModel, operation, settings);
var parametersString = string.Join(", ", parameters);

this.docGenerator.AppendMethodDocumentation(operationModel, code);
this.docGenerator.AppendMethodDocumentation(operationModel, IsApiResponseType(returnType), code);
GenerateObsoleteAttribute(operation, code);
GenerateForMultipartFormData(operationModel, code);
GenerateAcceptHeaders(operations, operation, code);
Expand All @@ -77,6 +78,9 @@ private string GenerateInterfaceBody()

protected string GetTypeName(OpenApiOperation operation)
{
if (settings.ResponseTypeOverride.TryGetValue(operation.OperationId, out var type))
return type is null or "void" ? "Task" : $"Task<{WellKnownNamesspaces.TrimImportedNamespaces(type)}>";

var returnTypeParameter =
(new[] { "200", "201", "203", "206" })
.Where(operation.Responses.ContainsKey)
Expand Down Expand Up @@ -173,6 +177,20 @@ private string GetDefaultReturnType()
: "Task";
}

/// <summary>
/// Checks if the given return type is derived from <c>ApiResponse</c> or its interface.
/// </summary>
/// <param name="typeName">The name of the type to check.</param>
/// <returns>True if the type is an ApiResponse Task or similar, false otherwise.</returns>
protected static bool IsApiResponseType(string typeName)
{
return Regex.IsMatch(
typeName,
"(Task|IObservable)<(I)?ApiResponse(<[\\w<>]+>)?>",
RegexOptions.None,
TimeSpan.FromSeconds(1));
}

private string GetConfiguredReturnType(string returnTypeParameter)
{
return settings.ReturnIApiResponse
Expand Down
2 changes: 1 addition & 1 deletion src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public override RefitGeneratedCode GenerateCode()
var parameters = ParameterExtractor.GetParameters(operationModel, operation, settings);
var parametersString = string.Join(", ", parameters);

this.docGenerator.AppendMethodDocumentation(operationModel, sb);
this.docGenerator.AppendMethodDocumentation(operationModel, IsApiResponseType(returnType), sb);
GenerateObsoleteAttribute(operation, sb);
GenerateForMultipartFormData(operationModel, sb);
GenerateAcceptHeaders(operations, operation, sb);
Expand Down
2 changes: 1 addition & 1 deletion src/Refitter.Core/RefitMultipleInterfaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public override RefitGeneratedCode GenerateCode()
var parameters = ParameterExtractor.GetParameters(operationModel, operation, settings);
var parametersString = string.Join(", ", parameters);

this.docGenerator.AppendMethodDocumentation(operationModel, code);
this.docGenerator.AppendMethodDocumentation(operationModel, IsApiResponseType(returnType), code);
GenerateObsoleteAttribute(operation, code);
GenerateForMultipartFormData(operationModel, code);
GenerateAcceptHeaders(operations, operation, code);
Expand Down
6 changes: 6 additions & 0 deletions src/Refitter.Core/Settings/RefitGeneratorSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ public class RefitGeneratorSettings
/// </summary>
public bool ReturnIApiResponse { get; set; }

/// <summary>
/// Gets or sets a dictionary of operation ids and a specific response type that they should use. The type is
/// wrapped in a task, but otherwise unmodified (so make sure that the namespaces are imported or specified).
/// </summary>
public Dictionary<string, string> ResponseTypeOverride { get; set; } = new();

/// <summary>
/// Gets or sets a value indicating whether to generate operation headers.
/// </summary>
Expand Down
7 changes: 3 additions & 4 deletions src/Refitter.Core/XmlDocumentationGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ public void AppendInterfaceDocumentation(OpenApiOperation group, StringBuilder c
/// Appends XML docs for the given method to the given code builder.
/// </summary>
/// <param name="method">The NSwag model of the method's OpenAPI definition.</param>
/// <param name="hasApiResponse">Indicates whether the method returns an <c>ApiResponse</c>.</param>
/// <param name="code">The builder to append the documentation to.</param>
public void AppendMethodDocumentation(CSharpOperationModel method, StringBuilder code)
public void AppendMethodDocumentation(CSharpOperationModel method, bool hasApiResponse, StringBuilder code)
{
if (!_settings.GenerateXmlDocCodeComments)
return;
Expand All @@ -56,9 +57,7 @@ public void AppendMethodDocumentation(CSharpOperationModel method, StringBuilder
this.AppendXmlCommentBlock("summary", method.Summary, code);

if (!string.IsNullOrWhiteSpace(method.Description))
{
this.AppendXmlCommentBlock("remarks", method.Description, code);
}

foreach (var parameter in method.Parameters)
{
Expand All @@ -69,7 +68,7 @@ public void AppendMethodDocumentation(CSharpOperationModel method, StringBuilder
{ ["name"] = parameter.VariableName });
}

if (_settings.ReturnIApiResponse)
if (hasApiResponse)
{
this.AppendXmlCommentBlock("returns", this.BuildApiResponseDescription(method.Responses), code);
}
Expand Down
5 changes: 5 additions & 0 deletions src/Refitter.SourceGenerator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ The following is an example `.refitter` file
"addAutoGeneratedHeader": true, // Optional. Default=true
"addAcceptHeaders": true, // Optional. Default=true
"returnIApiResponse": false, // Optional. Default=false
"responseTypeOverride": { // Optional. Default={}
"File_Upload": "IApiResponse",
"File_Download": "System.Net.Http.HttpContent"
},
"generateOperationHeaders": true, // Optional. Default=true
"typeAccessibility": "Public", // Optional. Values=Public|Internal. Default=Public
"useCancellationTokens": false, // Optional. Default=false
Expand Down Expand Up @@ -117,6 +121,7 @@ The following is an example `.refitter` file
- `addAutoGeneratedHeader` - a boolean indicating whether XML doc comments should be generated. Default is `true`
- `addAcceptHeaders` - a boolean indicating whether to add accept headers [Headers("Accept: application/json")]. Default is `true`
- `returnIApiResponse` - a boolean indicating whether to return `IApiResponse<T>` objects. Default is `false`
- `responseTypeOverride` - a dictionary with operation ids (as specified in the OpenAPI document) and a particular return type to use. The types are wrapped in a task, but otherwise unmodified (so make sure to specify or import their namespaces). Default is `{}`
- `generateOperationHeaders` - a boolean indicating whether to use operation headers in the generated methods. Default is `true`
- `typeAccessibility` - the generated type accessibility. Possible values are `Public` and `Internal`. Default is `Public`
- `useCancellationTokens` - Use cancellation tokens in the generated methods. Default is `false`
Expand Down
23 changes: 23 additions & 0 deletions src/Refitter.Tests/SwaggerPetstoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,29 @@ public async Task Can_Generate_Code_That_Returns_IApiResponse(SampleOpenSpecific
generateCode.Should().Contain("Task<IApiResponse<Pet>>");
}

[Theory]
[InlineData(SampleOpenSpecifications.SwaggerPetstoreJsonV3, "SwaggerPetstore.json")]
[InlineData(SampleOpenSpecifications.SwaggerPetstoreYamlV3, "SwaggerPetstore.yaml")]
[InlineData(SampleOpenSpecifications.SwaggerPetstoreJsonV2, "SwaggerPetstore.json")]
[InlineData(SampleOpenSpecifications.SwaggerPetstoreYamlV2, "SwaggerPetstore.yaml")]
public async Task Can_Generate_Code_With_Type_Override(SampleOpenSpecifications version, string filename)
{
var settings = new RefitGeneratorSettings
{
ReturnIApiResponse = false,
ResponseTypeOverride =
{
["getPetById"] = "IApiResponse<Pet>", // Wrap existing type
["deletePet"] = "Pet", // Add type where there was none
["addPet"] = "void", // Remove type
},
};
var generateCode = await GenerateCode(version, filename, settings);
generateCode.Should().Contain("Task<IApiResponse<Pet>> GetPetById");
generateCode.Should().Contain("Task<Pet> DeletePet");
generateCode.Should().Contain("Task AddPet");
}

[Theory]
[InlineData(SampleOpenSpecifications.SwaggerPetstoreJsonV3, "SwaggerPetstore.json")]
[InlineData(SampleOpenSpecifications.SwaggerPetstoreYamlV3, "SwaggerPetstore.yaml")]
Expand Down
20 changes: 10 additions & 10 deletions src/Refitter.Tests/XmlDocumentationGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void Can_Generate_Method_Summary()
{
var docs = new StringBuilder();
var method = CreateOperationModel(new OpenApiOperation { Summary = "TestSummary", });
this._generator.AppendMethodDocumentation(method, docs);
this._generator.AppendMethodDocumentation(method, false, docs);
docs.ToString().Trim().Should().StartWith("/// <summary>TestSummary</summary>");
}

Expand All @@ -63,7 +63,7 @@ public void Can_Generate_Method_Remarks()
{
var docs = new StringBuilder();
var method = CreateOperationModel(new OpenApiOperation { Description = "TestDescription", });
this._generator.AppendMethodDocumentation(method, docs);
this._generator.AppendMethodDocumentation(method, false, docs);
docs.ToString().Should().Contain("/// <remarks>TestDescription</remarks>");
}

Expand All @@ -74,7 +74,7 @@ public void Can_Generate_Method_Param()
var method = CreateOperationModel(new OpenApiOperation {
Parameters = { new OpenApiParameter { OriginalName = "testParam", Description = "TestParameter" } },
});
this._generator.AppendMethodDocumentation(method, docs);
this._generator.AppendMethodDocumentation(method, false, docs);
docs.ToString().Should().Contain("/// <param name=\"testParam\">TestParameter</param>");
}

Expand All @@ -93,7 +93,7 @@ public void Can_Generate_Method_Returns()
},
Produces = ["application/json"],
});
this._generator.AppendMethodDocumentation(method, docs);
this._generator.AppendMethodDocumentation(method, false, docs);
docs.ToString().Should().Contain("/// <returns>TestResponse</returns>");
}

Expand All @@ -108,7 +108,7 @@ public void Can_Generate_Method_Returns_With_Empty_Result()
},
Produces = ["application/json"],
});
this._generator.AppendMethodDocumentation(method, docs);
this._generator.AppendMethodDocumentation(method, false, docs);
docs.ToString().Should().Contain("/// <returns>")
.And.Contain("Task");
}
Expand All @@ -118,7 +118,7 @@ public void Can_Generate_Method_Returns_Without_Result()
{
var docs = new StringBuilder();
var method = CreateOperationModel(new OpenApiOperation());
this._generator.AppendMethodDocumentation(method, docs);
this._generator.AppendMethodDocumentation(method, false, docs);
docs.ToString().Should().Contain("/// <returns>")
.And.Contain("Task");
}
Expand All @@ -128,7 +128,7 @@ public void Can_Generate_Method_Throws()
{
var docs = new StringBuilder();
var method = CreateOperationModel(new OpenApiOperation());
this._generator.AppendMethodDocumentation(method, docs);
this._generator.AppendMethodDocumentation(method, false, docs);
docs.ToString().Should().Contain("/// <throws cref=\"ApiException\">");
}

Expand All @@ -145,7 +145,7 @@ public void Can_Generate_Method_Throws_With_Response_Code()
{
Responses = { ["400"] = new OpenApiResponse { Description = "TestResponse" } },
});
this._generator.AppendMethodDocumentation(method, docs);
this._generator.AppendMethodDocumentation(method, false, docs);
docs.ToString().Should().Contain("/// <throws cref=\"ApiException\">")
.And.Contain("/// 400: TestResponse");
}
Expand All @@ -163,7 +163,7 @@ public void Can_Generate_Method_Throws_Without_Response_Code()
{
Responses = { ["400"] = new OpenApiResponse { Description = "TestResponse" } },
});
this._generator.AppendMethodDocumentation(method, docs);
this._generator.AppendMethodDocumentation(method, false, docs);
docs.ToString().Should().Contain("/// <throws cref=\"ApiException\">")
.And.NotContain("/// 400: TestResponse");
}
Expand All @@ -181,7 +181,7 @@ public void Can_Generate_Method_With_IApiResponse()
{
Responses = { ["400"] = new OpenApiResponse { Description = "TestResponse" } },
});
this._generator.AppendMethodDocumentation(method, docs);
this._generator.AppendMethodDocumentation(method, true, docs);
docs.ToString().Should().NotContain("/// <throws cref=\"ApiException\">")
.And.Contain("/// <returns>")
.And.Contain("/// 400: TestResponse");
Expand Down
5 changes: 5 additions & 0 deletions src/Refitter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ The following is an example `.refitter` file
"addAutoGeneratedHeader": true, // Optional. Default=true
"addAcceptHeaders": true, // Optional. Default=true
"returnIApiResponse": false, // Optional. Default=false
"responseTypeOverride": { // Optional. Default={}
"File_Upload": "IApiResponse",
"File_Download": "System.Net.Http.HttpContent"
},
"generateOperationHeaders": true, // Optional. Default=true
"typeAccessibility": "Public", // Optional. Values=Public|Internal. Default=Public
"useCancellationTokens": false, // Optional. Default=false
Expand Down Expand Up @@ -189,6 +193,7 @@ The following is an example `.refitter` file
- `addAutoGeneratedHeader` - a boolean indicating whether XML doc comments should be generated. Default is `true`
- `addAcceptHeaders` - a boolean indicating whether to add accept headers [Headers("Accept: application/json")]. Default is `true`
- `returnIApiResponse` - a boolean indicating whether to return `IApiResponse<T>` objects. Default is `false`
- `responseTypeOverride` - a dictionary with operation ids (as specified in the OpenAPI document) and a particular return type to use. The types are wrapped in a task, but otherwise unmodified (so make sure to specify or import their namespaces). Default is `{}`
- `generateOperationHeaders` - a boolean indicating whether to use operation headers in the generated methods. Default is `true`
- `typeAccessibility` - the generated type accessibility. Possible values are `Public` and `Internal`. Default is `Public`
- `useCancellationTokens` - Use cancellation tokens in the generated methods. Default is `false`
Expand Down

0 comments on commit 6806827

Please sign in to comment.