Skip to content
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
10 changes: 10 additions & 0 deletions src/Refitter.Core/IdentifierUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,14 @@ public static string Sanitize(this string value)
return string.Join(string.Empty, value.Split(IllegalSymbols, StringSplitOptions.RemoveEmptyEntries))
.Trim(dash);
}

/// <summary>
/// Sanitizes and formats controller tags for identifier usage.
/// </summary>
/// <param name="tag">The tag to sanitize.</param>
/// <returns>A sanitized, title-cased identifier string.</returns>
public static string SanitizeControllerTag(this string tag)
{
return tag.Sanitize().CapitalizeFirstCharacter();
}
}
2 changes: 1 addition & 1 deletion src/Refitter.Core/RefitInterfaceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ private string GenerateInterfaceDeclaration(out string interfaceName)

var modifier = settings.TypeAccessibility.ToString().ToLowerInvariant();
var code = new StringBuilder();
docGenerator.AppendInterfaceDocumentation(document, code);
docGenerator.AppendSingleInterfaceDocumentation(document, code);
code.Append($"""
{Separator}{GetGeneratedCodeAttribute()}
{Separator}{modifier} partial interface {interfaceName}{inheritance}
Expand Down
5 changes: 2 additions & 3 deletions src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public override IEnumerable<GeneratedCode> GenerateCode()
if (!interfacesByGroup.TryGetValue(kv.Key, out var sb))
{
interfacesByGroup[kv.Key] = sb = new StringBuilder();
this.docGenerator.AppendInterfaceDocumentation(operation, sb);
this.docGenerator.AppendInterfaceDocumentationByTag(document, kv.Key, sb);

interfaceName = GetInterfaceName(kv.Key);
sb.AppendLine($$"""
Expand Down Expand Up @@ -131,8 +131,7 @@ private string GetGroupName(OpenApiOperation operation, string ungroupedTitle)
{
if (operation.Tags.FirstOrDefault() is string group && !string.IsNullOrWhiteSpace(group))
{
return IdentifierUtils.Sanitize(group)
.CapitalizeFirstCharacter();
return group.SanitizeControllerTag();
}

return ungroupedTitle;
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 @@ -36,7 +36,7 @@ public override IEnumerable<GeneratedCode> GenerateCode()
: "Execute";
var code = new StringBuilder();

this.docGenerator.AppendInterfaceDocumentation(operation, code);
this.docGenerator.AppendInterfaceDocumentationByEndpoint(operation, code);

var interfaceName = GetInterfaceName(kv, verb, operation);
code.AppendLine($$"""
Expand Down
58 changes: 43 additions & 15 deletions src/Refitter.Core/XmlDocumentationGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
/// </summary>
private const string Separator = " ";

/// <summary>
/// The name of the XML documentation tag used for summaries.
/// </summary>
private const string SummaryTag = "summary";

/// <summary>
/// Instantiates a new instance of the <see cref="XmlDocumentationGenerator"/> class.
/// </summary>
Expand All @@ -29,39 +34,62 @@
}

/// <summary>
/// Appends XML docs for the given interface definition to the given code builder.
/// This uses the OpenAPI operation info to generate the summary and remarks.
/// Generates an interface description from the tags of the given OpenAPI document and appends it to the builder.
/// </summary>
/// <param name="document">The parent document of the controller.</param>
/// <param name="tag">The controller tag that the endpoints were grouped by.</param>
/// <param name="code">The builder to append the documentation to.</param>
public void AppendInterfaceDocumentationByTag(OpenApiDocument document, string tag, StringBuilder code)
{
if (!_settings.GenerateXmlDocCodeComments)
{
return;
}

var controllerTag = document.Tags.FirstOrDefault(t => t.Name.SanitizeControllerTag() == tag);
var controllerDescription = controllerTag?.Description;
if (!string.IsNullOrEmpty(controllerDescription))
{
this.AppendXmlCommentBlock(SummaryTag, EscapeSymbols(controllerDescription), code, indent: Separator);

Check warning on line 53 in src/Refitter.Core/XmlDocumentationGenerator.cs

View workflow job for this annotation

GitHub Actions / 👌 Verify build

Possible null reference argument for parameter 'input' in 'string XmlDocumentationGenerator.EscapeSymbols(string input)'.

Check warning on line 53 in src/Refitter.Core/XmlDocumentationGenerator.cs

View workflow job for this annotation

GitHub Actions / 👌 Verify build

Possible null reference argument for parameter 'input' in 'string XmlDocumentationGenerator.EscapeSymbols(string input)'.

Check warning on line 53 in src/Refitter.Core/XmlDocumentationGenerator.cs

View workflow job for this annotation

GitHub Actions / script

Possible null reference argument for parameter 'input' in 'string XmlDocumentationGenerator.EscapeSymbols(string input)'.

Check warning on line 53 in src/Refitter.Core/XmlDocumentationGenerator.cs

View workflow job for this annotation

GitHub Actions / 👌 Verify build

Possible null reference argument for parameter 'input' in 'string XmlDocumentationGenerator.EscapeSymbols(string input)'.
}
}

/// <summary>
/// Generates an interface description from the summary of the given endpoint and appends it to the builder.
/// </summary>
/// <param name="group">The OpenAPI definition of the interface.</param>
/// <param name="endpoint">The OpenAPI definition of the endpoint.</param>
/// <param name="code">The builder to append the documentation to.</param>
public void AppendInterfaceDocumentation(OpenApiOperation group, StringBuilder code)
public void AppendInterfaceDocumentationByEndpoint(OpenApiOperation endpoint, StringBuilder code)
{
if (!_settings.GenerateXmlDocCodeComments)
{
return;
}

this.AppendXmlCommentBlock("summary", group?.Summary ?? "No summary available", code, indent: Separator);
var summary = endpoint.Summary;
if (!string.IsNullOrEmpty(summary))
{
this.AppendXmlCommentBlock(SummaryTag, EscapeSymbols(summary), code, indent: Separator);
}
}

/// <summary>
/// Appends XML docs for the given interface definition to the given code builder.
/// This uses the OpenAPI document's info description as the summary.
/// Generates an interface description from the title of the given document and appends it to the builder.
/// </summary>
/// <param name="document">The OpenAPI definition of the interface.</param>
/// <param name="document">The OpenAPI definition of the document.</param>
/// <param name="code">The builder to append the documentation to.</param>
public void AppendInterfaceDocumentation(OpenApiDocument document, StringBuilder code)
public void AppendSingleInterfaceDocumentation(OpenApiDocument document, StringBuilder code)
{
if (!_settings.GenerateXmlDocCodeComments)
{
return;
}

this.AppendXmlCommentBlock(
"summary",
document.Info?.Title ?? "Refit interface - no description available",
code,
indent: Separator);
var title = document.Info?.Title;
if (!string.IsNullOrEmpty(title))
{
this.AppendXmlCommentBlock(SummaryTag, EscapeSymbols(title), code, indent: Separator);

Check warning on line 91 in src/Refitter.Core/XmlDocumentationGenerator.cs

View workflow job for this annotation

GitHub Actions / 👌 Verify build

Possible null reference argument for parameter 'input' in 'string XmlDocumentationGenerator.EscapeSymbols(string input)'.

Check warning on line 91 in src/Refitter.Core/XmlDocumentationGenerator.cs

View workflow job for this annotation

GitHub Actions / 👌 Verify build

Possible null reference argument for parameter 'input' in 'string XmlDocumentationGenerator.EscapeSymbols(string input)'.

Check warning on line 91 in src/Refitter.Core/XmlDocumentationGenerator.cs

View workflow job for this annotation

GitHub Actions / script

Possible null reference argument for parameter 'input' in 'string XmlDocumentationGenerator.EscapeSymbols(string input)'.

Check warning on line 91 in src/Refitter.Core/XmlDocumentationGenerator.cs

View workflow job for this annotation

GitHub Actions / 👌 Verify build

Possible null reference argument for parameter 'input' in 'string XmlDocumentationGenerator.EscapeSymbols(string input)'.
}
}

/// <summary>
Expand All @@ -85,7 +113,7 @@
return;

if (!string.IsNullOrWhiteSpace(method.Summary))
this.AppendXmlCommentBlock("summary", EscapeSymbols(method.Summary), code);
this.AppendXmlCommentBlock(SummaryTag, EscapeSymbols(method.Summary), code);

if (!string.IsNullOrWhiteSpace(method.Description))
this.AppendXmlCommentBlock("remarks", EscapeSymbols(method.Description), code);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,6 @@ public partial interface IUpdatePetWithFormEndpoint
public partial interface IDeletePetEndpoint
{
/// <summary>Deletes a pet</summary>
/// <param name="api_key">api_key parameter</param>
/// <param name="petId">Pet id to delete</param>
/// <param name="cancellationToken">The cancellation token to cancel the request.</param>
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
Expand Down Expand Up @@ -219,7 +218,6 @@ public partial interface IUploadFileEndpoint
/// <summary>uploads an image</summary>
/// <param name="petId">ID of pet to update</param>
/// <param name="additionalMetadata">Additional Metadata</param>
/// <param name="body">body parameter</param>
/// <param name="cancellationToken">The cancellation token to cancel the request.</param>
/// <returns>
/// A <see cref="Task"/> representing the <see cref="IApiResponse"/> instance containing the result:
Expand Down Expand Up @@ -258,7 +256,6 @@ public partial interface IPlaceOrderEndpoint
{
/// <summary>Place an order for a pet</summary>
/// <remarks>Place a new order in the store</remarks>
/// <param name="body">body parameter</param>
/// <param name="cancellationToken">The cancellation token to cancel the request.</param>
/// <returns>successful operation</returns>
/// <exception cref="ApiException">
Expand Down Expand Up @@ -360,7 +357,6 @@ public partial interface ICreateUsersWithListInputEndpoint
{
/// <summary>Creates list of users with given input array</summary>
/// <remarks>Creates list of users with given input array</remarks>
/// <param name="body">body parameter</param>
/// <param name="cancellationToken">The cancellation token to cancel the request.</param>
/// <returns>Successful operation</returns>
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace Refitter.Tests.AdditionalFiles.ByTag
{
/// <summary>Update an existing pet</summary>
/// <summary>Everything about your Pets</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IPetApi
{
Expand Down Expand Up @@ -195,7 +195,7 @@ public partial interface IPetApi
Task<ApiResponse> UploadFile(long petId, [Query] string additionalMetadata, StreamPart body);
}

/// <summary>Returns pet inventories by status</summary>
/// <summary>Operations about user</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IStoreApi
{
Expand Down Expand Up @@ -276,7 +276,7 @@ public partial interface IStoreApi
Task DeleteOrder(long orderId);
}

/// <summary>Create user</summary>
/// <summary>Access to Petstore orders</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IUserApi
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

namespace Refitter.Tests.UseJsonInheritanceConverter
{
/// <summary>Refit interface - no description available</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IApiClient
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

namespace Refitter.Tests.UsePolymorphicSerialization
{
/// <summary>Refit interface - no description available</summary>
[System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
public partial interface IApiClient
{
Expand Down
16 changes: 14 additions & 2 deletions src/Refitter.Tests/XmlDocumentationGeneratorTests.cs
Comment thread
DJ4ddi marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void Can_Generate_Interface_Doc_Without_Linebreaks()
{
var docs = new StringBuilder();
var interfaceDefinition = new OpenApiOperation { Summary = "Test", };
this._generator.AppendInterfaceDocumentation(interfaceDefinition, docs);
this._generator.AppendInterfaceDocumentationByEndpoint(interfaceDefinition, docs);
docs.ToString().Trim().Should().Be("/// <summary>Test</summary>");
}

Expand All @@ -31,12 +31,24 @@ public void Can_Generate_Interface_Doc_With_Linebreaks()
{
var docs = new StringBuilder();
var interfaceDefinition = new OpenApiOperation { Summary = "Test\n", };
this._generator.AppendInterfaceDocumentation(interfaceDefinition, docs);
this._generator.AppendInterfaceDocumentationByEndpoint(interfaceDefinition, docs);
docs.ToString().Trim().Should().NotBe("/// <summary>Test</summary>");
docs.ToString().Trim().Should().Contain("<summary>")
.And.Contain("Test");
}

[Test]
public void Can_Generate_Interface_Doc_From_Controller_Tag()
{
var docs = new StringBuilder();
var controllerTag = new OpenApiTag { Name = "TestController", Description = "TestControllerDescription" };
var document = new OpenApiDocument { Tags = [controllerTag] };

this._generator.AppendInterfaceDocumentationByTag(document, "TestController", docs);

docs.ToString().Trim().Should().Be("/// <summary>TestControllerDescription</summary>");
}
Comment on lines +40 to +50

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding test cases for edge scenarios such as: tags with null or empty descriptions, tags that don't exist in the document, and tag descriptions containing XML special characters (e.g., "<", ">", "&") to verify the EscapeSymbols functionality works correctly in this context.

Copilot uses AI. Check for mistakes.

[Test]
public void Can_Generate_Method_Summary()
{
Expand Down
Loading