diff --git a/src/Refitter.Core/IdentifierUtils.cs b/src/Refitter.Core/IdentifierUtils.cs index 223bb9fde..a774f8db8 100644 --- a/src/Refitter.Core/IdentifierUtils.cs +++ b/src/Refitter.Core/IdentifierUtils.cs @@ -53,4 +53,14 @@ public static string Sanitize(this string value) return string.Join(string.Empty, value.Split(IllegalSymbols, StringSplitOptions.RemoveEmptyEntries)) .Trim(dash); } + + /// + /// Sanitizes and formats controller tags for identifier usage. + /// + /// The tag to sanitize. + /// A sanitized, title-cased identifier string. + public static string SanitizeControllerTag(this string tag) + { + return tag.Sanitize().CapitalizeFirstCharacter(); + } } diff --git a/src/Refitter.Core/RefitInterfaceGenerator.cs b/src/Refitter.Core/RefitInterfaceGenerator.cs index dc5623080..68cb74d0e 100644 --- a/src/Refitter.Core/RefitInterfaceGenerator.cs +++ b/src/Refitter.Core/RefitInterfaceGenerator.cs @@ -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} diff --git a/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs b/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs index d379a16c3..b3b2afae9 100644 --- a/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs +++ b/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs @@ -49,7 +49,7 @@ public override IEnumerable 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($$""" @@ -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; diff --git a/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs b/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs index 6f744fcf2..224356a30 100644 --- a/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs +++ b/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs @@ -36,7 +36,7 @@ public override IEnumerable GenerateCode() : "Execute"; var code = new StringBuilder(); - this.docGenerator.AppendInterfaceDocumentation(operation, code); + this.docGenerator.AppendInterfaceDocumentationByEndpoint(operation, code); var interfaceName = GetInterfaceName(kv, verb, operation); code.AppendLine($$""" diff --git a/src/Refitter.Core/XmlDocumentationGenerator.cs b/src/Refitter.Core/XmlDocumentationGenerator.cs index c492ab7c9..9f798000a 100644 --- a/src/Refitter.Core/XmlDocumentationGenerator.cs +++ b/src/Refitter.Core/XmlDocumentationGenerator.cs @@ -19,6 +19,11 @@ public class XmlDocumentationGenerator /// private const string Separator = " "; + /// + /// The name of the XML documentation tag used for summaries. + /// + private const string SummaryTag = "summary"; + /// /// Instantiates a new instance of the class. /// @@ -29,39 +34,62 @@ internal XmlDocumentationGenerator(RefitGeneratorSettings settings) } /// - /// 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. + /// + /// The parent document of the controller. + /// The controller tag that the endpoints were grouped by. + /// The builder to append the documentation to. + 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); + } + } + + /// + /// Generates an interface description from the summary of the given endpoint and appends it to the builder. /// - /// The OpenAPI definition of the interface. + /// The OpenAPI definition of the endpoint. /// The builder to append the documentation to. - 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); + } } /// - /// 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. /// - /// The OpenAPI definition of the interface. + /// The OpenAPI definition of the document. /// The builder to append the documentation to. - 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); + } } /// @@ -85,7 +113,7 @@ public void AppendMethodDocumentation( 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); diff --git a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfaces.g.cs b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfaces.g.cs index f9aba24c0..cae33f5ad 100644 --- a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfaces.g.cs +++ b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfaces.g.cs @@ -191,7 +191,6 @@ public partial interface IUpdatePetWithFormEndpoint public partial interface IDeletePetEndpoint { /// Deletes a pet - /// api_key parameter /// Pet id to delete /// The cancellation token to cancel the request. /// A that completes when the request is finished. @@ -219,7 +218,6 @@ public partial interface IUploadFileEndpoint /// uploads an image /// ID of pet to update /// Additional Metadata - /// body parameter /// The cancellation token to cancel the request. /// /// A representing the instance containing the result: @@ -258,7 +256,6 @@ public partial interface IPlaceOrderEndpoint { /// Place an order for a pet /// Place a new order in the store - /// body parameter /// The cancellation token to cancel the request. /// successful operation /// @@ -360,7 +357,6 @@ public partial interface ICreateUsersWithListInputEndpoint { /// Creates list of users with given input array /// Creates list of users with given input array - /// body parameter /// The cancellation token to cancel the request. /// Successful operation /// Thrown when the request returns a non-success status code. diff --git a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfacesByTag.g.cs b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfacesByTag.g.cs index f6ab4e9ef..c950f32c2 100644 --- a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfacesByTag.g.cs +++ b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfacesByTag.g.cs @@ -14,7 +14,7 @@ namespace Refitter.Tests.AdditionalFiles.ByTag { - /// Update an existing pet + /// Everything about your Pets [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IPetApi { @@ -195,7 +195,7 @@ public partial interface IPetApi Task UploadFile(long petId, [Query] string additionalMetadata, StreamPart body); } - /// Returns pet inventories by status + /// Operations about user [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IStoreApi { @@ -276,7 +276,7 @@ public partial interface IStoreApi Task DeleteOrder(long orderId); } - /// Create user + /// Access to Petstore orders [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IUserApi { diff --git a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UseJsonInheritanceConverter.g.cs b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UseJsonInheritanceConverter.g.cs index 3e9b0f840..81e0efe25 100644 --- a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UseJsonInheritanceConverter.g.cs +++ b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UseJsonInheritanceConverter.g.cs @@ -12,7 +12,6 @@ namespace Refitter.Tests.UseJsonInheritanceConverter { - /// Refit interface - no description available [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IApiClient { diff --git a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UsePolymorphicSerialization.g.cs b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UsePolymorphicSerialization.g.cs index b20a2322a..9bb27f74b 100644 --- a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UsePolymorphicSerialization.g.cs +++ b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UsePolymorphicSerialization.g.cs @@ -12,7 +12,6 @@ namespace Refitter.Tests.UsePolymorphicSerialization { - /// Refit interface - no description available [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IApiClient { diff --git a/src/Refitter.Tests/XmlDocumentationGeneratorTests.cs b/src/Refitter.Tests/XmlDocumentationGeneratorTests.cs index eaf8f6568..2c3550321 100644 --- a/src/Refitter.Tests/XmlDocumentationGeneratorTests.cs +++ b/src/Refitter.Tests/XmlDocumentationGeneratorTests.cs @@ -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("/// Test"); } @@ -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("/// Test"); docs.ToString().Trim().Should().Contain("") .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("/// TestControllerDescription"); + } + [Test] public void Can_Generate_Method_Summary() {