From 2af900331f0282d4e60347b0aacc147d99ddc19c Mon Sep 17 00:00:00 2001 From: Chebotov Nikolay Date: Thu, 26 Aug 2021 16:38:36 +0300 Subject: [PATCH] update: Add `IncludeXmlCommentsWithRemarks` improvements --- CHANGELOG.md | 4 ++ .../Extensions/EnumTypeExtensions.cs | 2 +- .../Extensions/XmlCommentsExtensions.cs | 24 ++++--- .../Filters/InheritDocParameterFilter.cs | 46 +++++++++++-- .../Filters/InheritDocRequestBodyFilter.cs | 41 ++++++++++-- .../Filters/InheritDocSchemaFilter.cs | 21 ++++-- ...e.Swashbuckle.AspNetCore.Extensions.csproj | 6 +- .../Controllers/TodoController.cs | 10 +++ .../Models/AddSomeCommand.cs | 65 ++++++++++++++++++ .../WebApi3.1-Swashbuckle.xml | 66 +++++++++++++++++++ 10 files changed, 256 insertions(+), 29 deletions(-) create mode 100644 test/WebApi3.1-Swashbuckle/Models/AddSomeCommand.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index fcf5d8d..3a00deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ These are the changes to each version that has been released on the [nuget](https://www.nuget.org/packages/Unchase.Swashbuckle.AspNetCore.Extensions/). +## v2.6.6 `2021-08-26` + +- [x] Add `IncludeXmlCommentsWithRemarks` improvements + ## v2.6.3 `2021-08-26` - [x] Fix `IncludeXmlCommentsWithRemarks` diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/EnumTypeExtensions.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/EnumTypeExtensions.cs index 8d86635..d07d791 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/EnumTypeExtensions.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/EnumTypeExtensions.cs @@ -139,7 +139,7 @@ private static string QualifiedNameFor(Type type, bool expandGenericArgs = false return QualifiedNameFor(type.GetElementType(), expandGenericArgs) + "[]"; var stringBuilder = new StringBuilder(); - if (!string.IsNullOrEmpty(type.Namespace)) + if (!string.IsNullOrWhiteSpace(type.Namespace)) stringBuilder.Append(type.Namespace + "."); if (type.IsNested) diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/XmlCommentsExtensions.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/XmlCommentsExtensions.cs index 63ad45b..19061ed 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/XmlCommentsExtensions.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/XmlCommentsExtensions.cs @@ -57,7 +57,7 @@ private static Type[] GetTargets(Type type, string cref) } // Try to find the target, if one is declared. - if (!string.IsNullOrEmpty(cref)) + if (!string.IsNullOrWhiteSpace(cref)) { var crefTarget = targets.SingleOrDefault(t => XmlCommentsNodeNameHelper.GetMemberNameForType(t) == cref); @@ -124,7 +124,7 @@ private static MemberInfo[] GetTargets(MemberInfo memberInfo, string cref) .ToList(); // Try to find the target, if one is declared. - if (!string.IsNullOrEmpty(cref)) + if (!string.IsNullOrWhiteSpace(cref)) { var crefTarget = targets.SingleOrDefault(t => XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(t) == cref); @@ -164,13 +164,21 @@ internal static void ApplyPropertyComments( } string cref = inheritedDocs[memberName]; - var target = memberInfo.GetTargetRecursive(inheritedDocs, cref); - if (target == null) + XPathNavigator targetXmlNode; + if (string.IsNullOrWhiteSpace(cref)) { - return; - } + var target = memberInfo.GetTargetRecursive(inheritedDocs, cref); + if (target == null) + { + return; + } - var targetXmlNode = GetMemberXmlNode(XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(target), documents); + targetXmlNode = GetMemberXmlNode(XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(target), documents); + } + else + { + targetXmlNode = GetMemberXmlNode(cref, documents); + } if (targetXmlNode == null) { @@ -178,7 +186,7 @@ internal static void ApplyPropertyComments( } var summaryNode = targetXmlNode.SelectSingleNode(SummaryTag); - if (summaryNode != null) + if (summaryNode != null && string.IsNullOrWhiteSpace(schema.Description)) { schema.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml); diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocParameterFilter.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocParameterFilter.cs index 60f60b3..1f483c2 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocParameterFilter.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocParameterFilter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Xml.XPath; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; @@ -82,19 +83,26 @@ public void Apply(OpenApiParameter parameter, ParameterFilterContext context) // Try to apply a description for inherited types. string parameterMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(context.ApiParameterDescription.PropertyInfo()); - if (string.IsNullOrEmpty(parameter.Description) && _inheritedDocs.ContainsKey(parameterMemberName)) + if (string.IsNullOrWhiteSpace(parameter.Description) && _inheritedDocs.ContainsKey(parameterMemberName)) { string cref = _inheritedDocs[parameterMemberName]; - var target = context.ApiParameterDescription.PropertyInfo().GetTargetRecursive(_inheritedDocs, cref); + XPathNavigator targetXmlNode; + if (string.IsNullOrWhiteSpace(cref)) + { + var target = context.ApiParameterDescription.PropertyInfo().GetTargetRecursive(_inheritedDocs, cref); + if (target == null) + { + return; + } - if (target == null) + targetXmlNode = XmlCommentsExtensions.GetMemberXmlNode(XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(target), _documents); + } + else { - return; + targetXmlNode = XmlCommentsExtensions.GetMemberXmlNode(cref, _documents); } - var targetXmlNode = XmlCommentsExtensions.GetMemberXmlNode(XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(target), _documents); var summaryNode = targetXmlNode?.SelectSingleNode(SummaryTag); - if (summaryNode != null) { parameter.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml); @@ -109,6 +117,32 @@ public void Apply(OpenApiParameter parameter, ParameterFilterContext context) } } } + + // TODO + var type = context.ApiParameterDescription.PropertyInfo()?.DeclaringType; + var typeName = type?.Name; + if (!string.IsNullOrWhiteSpace(typeName)) + { + if (context.SchemaRepository.Schemas.ContainsKey(typeName)) + { + var schema = context.SchemaRepository.Schemas[typeName]; + if (schema?.Properties?.Any() != true) + { + return; + } + + // Add the summary and examples for the properties. + foreach (var entry in schema.Properties) + { + var memberInfo = ((TypeInfo)type).DeclaredMembers.FirstOrDefault(p => + p.Name.Equals(entry.Key, StringComparison.OrdinalIgnoreCase)); + if (memberInfo != null) + { + entry.Value.ApplyPropertyComments(memberInfo, _documents, _inheritedDocs, _includeRemarks, _excludedTypes); + } + } + } + } } #endregion diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocRequestBodyFilter.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocRequestBodyFilter.cs index 0192c10..c46945b 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocRequestBodyFilter.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocRequestBodyFilter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Xml.XPath; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; @@ -72,14 +73,26 @@ public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext conte // Try to apply a description for inherited types. string parameterMemberName = XmlCommentsNodeNameHelper.GetMemberNameForType(context.BodyParameterDescription.Type); - if (string.IsNullOrEmpty(requestBody.Description) && _inheritedDocs.ContainsKey(parameterMemberName)) + if (string.IsNullOrWhiteSpace(requestBody.Description) && _inheritedDocs.ContainsKey(parameterMemberName)) { string cref = _inheritedDocs[parameterMemberName]; - var target = context.BodyParameterDescription.Type.GetTargetRecursive(_inheritedDocs, cref); + XPathNavigator targetXmlNode; + if (string.IsNullOrWhiteSpace(cref)) + { + var target = context.BodyParameterDescription.Type.GetTargetRecursive(_inheritedDocs, cref); + if (target == null) + { + return; + } - var targetXmlNode = XmlCommentsExtensions.GetMemberXmlNode(XmlCommentsNodeNameHelper.GetMemberNameForType(target), _documents); - var summaryNode = targetXmlNode?.SelectSingleNode(SummaryTag); + targetXmlNode = XmlCommentsExtensions.GetMemberXmlNode(XmlCommentsNodeNameHelper.GetMemberNameForType(target), _documents); + } + else + { + targetXmlNode = XmlCommentsExtensions.GetMemberXmlNode(cref, _documents); + } + var summaryNode = targetXmlNode?.SelectSingleNode(SummaryTag); if (summaryNode != null) { requestBody.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml); @@ -94,6 +107,26 @@ public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext conte } } } + + if (context.SchemaRepository.Schemas.ContainsKey(context.BodyParameterDescription.Type.Name)) + { + var schema = context.SchemaRepository.Schemas[context.BodyParameterDescription.Type.Name]; + if (schema?.Properties?.Any() != true) + { + return; + } + + // Add the summary and examples for the properties. + foreach (var entry in schema.Properties) + { + var memberInfo = ((TypeInfo)context.BodyParameterDescription.Type).DeclaredMembers.FirstOrDefault(p => + p.Name.Equals(entry.Key, StringComparison.OrdinalIgnoreCase)); + if (memberInfo != null) + { + entry.Value.ApplyPropertyComments(memberInfo, _documents, _inheritedDocs, _includeRemarks, _excludedTypes); + } + } + } } #endregion diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocSchemaFilter.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocSchemaFilter.cs index 4c1399a..f00860c 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocSchemaFilter.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocSchemaFilter.cs @@ -73,19 +73,26 @@ public void Apply(OpenApiSchema schema, SchemaFilterContext context) // Try to apply a description for inherited types. string memberName = XmlCommentsNodeNameHelper.GetMemberNameForType(context.Type); - if (string.IsNullOrEmpty(schema.Description) && _inheritedDocs.ContainsKey(memberName)) + if (string.IsNullOrWhiteSpace(schema.Description) && _inheritedDocs.ContainsKey(memberName)) { string cref = _inheritedDocs[memberName]; - var target = context.Type.GetTargetRecursive(_inheritedDocs, cref); + XPathNavigator targetXmlNode; + if (string.IsNullOrWhiteSpace(cref)) + { + var target = context.Type.GetTargetRecursive(_inheritedDocs, cref); + if (target == null) + { + return; + } - if (target == null) + targetXmlNode = XmlCommentsExtensions.GetMemberXmlNode(XmlCommentsNodeNameHelper.GetMemberNameForType(target), _documents); + } + else { - return; + targetXmlNode = XmlCommentsExtensions.GetMemberXmlNode(cref, _documents); } - var targetXmlNode = XmlCommentsExtensions.GetMemberXmlNode(XmlCommentsNodeNameHelper.GetMemberNameForType(target), _documents); var summaryNode = targetXmlNode?.SelectSingleNode(SummaryTag); - if (summaryNode != null) { schema.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml); @@ -101,7 +108,7 @@ public void Apply(OpenApiSchema schema, SchemaFilterContext context) } } - if (schema.Properties == null) + if (schema.Properties?.Any() != true) { return; } diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.csproj b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.csproj index d1a50ac..dfc3812 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.csproj +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.csproj @@ -14,9 +14,9 @@ 7.3 https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions/blob/master/assets/icon.png?raw=true - 2.6.5 - 2.6.5.0 - 2.6.5.0 + 2.6.6 + 2.6.6.0 + 2.6.6.0 false Unchase.Swashbuckle.AspNetCore.Extensions.xml diff --git a/test/WebApi3.1-Swashbuckle/Controllers/TodoController.cs b/test/WebApi3.1-Swashbuckle/Controllers/TodoController.cs index 09f71aa..5e628a0 100644 --- a/test/WebApi3.1-Swashbuckle/Controllers/TodoController.cs +++ b/test/WebApi3.1-Swashbuckle/Controllers/TodoController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; using System.Collections.Generic; +using System.Threading.Tasks; using TodoApi.Models; using WebApi3._1_Swashbuckle.Contexts; using WebApi3._1_Swashbuckle.Models; @@ -149,5 +150,14 @@ public ActionResult CreateFromQuery([FromQuery] TodoItem item) return new CreatedResult(string.Empty, item); } + + /// + /// Add some command. + /// + [HttpPost("add-some-command")] + public IActionResult AddSomeCommand(AddSomeCommand command) + { + return Ok("Ok"); + } } } diff --git a/test/WebApi3.1-Swashbuckle/Models/AddSomeCommand.cs b/test/WebApi3.1-Swashbuckle/Models/AddSomeCommand.cs new file mode 100644 index 0000000..d5f426e --- /dev/null +++ b/test/WebApi3.1-Swashbuckle/Models/AddSomeCommand.cs @@ -0,0 +1,65 @@ +namespace WebApi3._1_Swashbuckle.Models +{ + /// + /// CommandType + /// + public class CommandType + { + /// + /// Name. + /// + public string Name { get; set; } + + /// + /// Description. + /// + public string Description { get; set; } + + /// + /// External Id. + /// + public string ExternalId { get; set; } + + /// + /// Is Read Only. + /// + /// + /// Is Read Only remarks. + /// + public bool IsReadOnly { get; set; } + + /// + /// Is Active. + /// + /// + /// Is Active remarks. + /// + public bool IsActive { get; set; } + } + + /// + /// AddSomeCommand. + /// + public class AddSomeCommand + { + /// + /// Example + public string Name { get; set; } + + /// + /// Example + public string Description { get; set; } + + /// + /// Example + public string ExternalId { get; set; } + + /// + /// false + public bool IsReadOnly { get; set; } + + /// + /// true + public bool IsActive { get; set; } + } +} \ No newline at end of file diff --git a/test/WebApi3.1-Swashbuckle/WebApi3.1-Swashbuckle.xml b/test/WebApi3.1-Swashbuckle/WebApi3.1-Swashbuckle.xml index 8544c8c..37f3b86 100644 --- a/test/WebApi3.1-Swashbuckle/WebApi3.1-Swashbuckle.xml +++ b/test/WebApi3.1-Swashbuckle/WebApi3.1-Swashbuckle.xml @@ -132,6 +132,72 @@ Returns the newly created item. If the item is null. + + + Add some command. + + + + + CommandType + + + + + Name. + + + + + Description. + + + + + External Id. + + + + + Is Read Only. + + + Is Read Only remarks. + + + + + Is Active. + + + Is Active remarks. + + + + + AddSomeCommand. + + + + + Example + + + + Example + + + + Example + + + + false + + + + true + Complicated class for testing enums