Skip to content

Commit

Permalink
Add "IncludeXEnumRemarks" option to include remarks for descriptions …
Browse files Browse the repository at this point in the history
…from xml-comments.
  • Loading branch information
unchase committed Oct 7, 2020
1 parent cb1dbba commit 970a850
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 18 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

These are the changes to each version that has been released on the [nuget](https://www.nuget.org/packages/Unchase.Swashbuckle.AspNetCore.Extensions/).

## v2.3.13 `2020-10-07`

- [x] Add `IncludeXEnumRemarks` option to include remarks for descriptions from xml-comments
- [x] Fix small bugs

## v2.3.12 `(2020-09-21)`

- [x] Fix [issue #11](https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions/issues/11)
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ public void ConfigureServices(IServiceCollection services)
// add descriptions from DescriptionAttribute or xml-comments to fix enums (add 'x-enumDescriptions' for schema extensions) for applied filters
o.IncludeDescriptions = true;

// add remarks for descriptions from xml-comments
o.IncludeXEnumRemarks = true;

// get descriptions from DescriptionAttribute then from xml-comments
o.DescriptionSource = DescriptionSources.DescriptionAttributesThenXmlComments;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ private static string GetFieldAttributeDescription(this Type enumType, object en
return string.Empty;
}

internal static List<OpenApiString> GetEnumValuesDescription(Type enumType, DescriptionSources descriptionSource, IEnumerable<XPathNavigator> xmlNavigators = null)
internal static List<OpenApiString> GetEnumValuesDescription(Type enumType, DescriptionSources descriptionSource, IEnumerable<XPathNavigator> xmlNavigators, bool includeRemarks = false)
{
var enumsDescriptions = new List<OpenApiString>();
foreach (var enumValue in Enum.GetValues(enumType))
Expand All @@ -66,15 +66,15 @@ internal static List<OpenApiString> GetEnumValuesDescription(Type enumType, Desc
case DescriptionSources.XmlComments:
var memberInfo = enumType.GetMembers().FirstOrDefault(m =>
m.Name.Equals(enumValue.ToString(), StringComparison.InvariantCultureIgnoreCase));
enumDescription = TryGetMemberComments(memberInfo, xmlNavigators);
enumDescription = TryGetMemberComments(memberInfo, xmlNavigators, includeRemarks);
break;
case DescriptionSources.DescriptionAttributesThenXmlComments:
enumDescription = GetDescriptionFromEnumOption(enumType, enumValue);
if (string.IsNullOrWhiteSpace(enumDescription))
{
var memberInfo2 = enumType.GetMembers().FirstOrDefault(m =>
m.Name.Equals(enumValue.ToString(), StringComparison.InvariantCultureIgnoreCase));
enumDescription = TryGetMemberComments(memberInfo2, xmlNavigators);
enumDescription = TryGetMemberComments(memberInfo2, xmlNavigators, includeRemarks);
}
break;
}
Expand All @@ -85,32 +85,77 @@ internal static List<OpenApiString> GetEnumValuesDescription(Type enumType, Desc
}
finally
{
enumsDescriptions.Add(new OpenApiString(enumDescription));
if (!string.IsNullOrWhiteSpace(enumDescription))
enumsDescriptions.Add(new OpenApiString(enumDescription));
}
}
return enumsDescriptions;
}

private static string TryGetMemberComments(MemberInfo memberInfo, IEnumerable<XPathNavigator> xmlNavigators)
private static string TryGetMemberComments(MemberInfo memberInfo, IEnumerable<XPathNavigator> xmlNavigators, bool includeRemarks = false)
{
var commentsBuilder = new StringBuilder();
if (xmlNavigators == null)
return string.Empty;

foreach (var xmlNavigator in xmlNavigators)
{
var nodeNameForMember = XmlCommentsNodeNameHelper.GetNodeNameForMember(memberInfo);
var xpathNavigator1 = xmlNavigator.SelectSingleNode(
var nodeNameForMember = GetNodeNameForMember(memberInfo);
var xpathMemberNavigator = xmlNavigator.SelectSingleNode(
$"/doc/members/member[@name='{nodeNameForMember}']");
var xpathNavigator2 = xpathNavigator1?.SelectSingleNode("summary");
if (xpathNavigator2 != null)
var xpathSummaryNavigator = xpathMemberNavigator?.SelectSingleNode("summary");
if (xpathSummaryNavigator != null)
{
return XmlCommentsTextHelper.Humanize(xpathNavigator2.InnerXml);
commentsBuilder.Append(XmlCommentsTextHelper.Humanize(xpathSummaryNavigator.InnerXml));
if (includeRemarks)
{
var xpathRemarksNavigator = xpathMemberNavigator?.SelectSingleNode("remarks");
if (xpathRemarksNavigator != null && !string.IsNullOrWhiteSpace(xpathRemarksNavigator.InnerXml))
{
commentsBuilder.Append($" ({XmlCommentsTextHelper.Humanize(xpathRemarksNavigator.InnerXml)})");
}
}

return commentsBuilder.ToString();
}
}

return string.Empty;
}

private static string GetNodeNameForMember(MemberInfo memberInfo)
{
var stringBuilder = new StringBuilder((memberInfo.MemberType & MemberTypes.Field) != 0 ? "F:" : "P:");
stringBuilder.Append(QualifiedNameFor(memberInfo.DeclaringType, false));
stringBuilder.Append("." + memberInfo.Name);
return stringBuilder.ToString();
}

private static string QualifiedNameFor(Type type, bool expandGenericArgs = false)
{
if (type.IsArray)
return QualifiedNameFor(type.GetElementType(), expandGenericArgs) + "[]";

var stringBuilder = new StringBuilder();
if (!string.IsNullOrEmpty(type.Namespace))
stringBuilder.Append(type.Namespace + ".");

if (type.IsNested)
stringBuilder.Append(type.DeclaringType?.Name + ".");

if (type.IsConstructedGenericType & expandGenericArgs)
{
var str = type.Name.Split('`').First();
stringBuilder.Append(str);
var values = type.GetGenericArguments().Select(t => !t.IsGenericParameter ? QualifiedNameFor(t, true) : string.Format("`{0}", t.GenericParameterPosition));
stringBuilder.Append("{" + string.Join(",", values) + "}");
}
else
stringBuilder.Append(type.Name);

return stringBuilder.ToString();
}

internal static string AddEnumValuesDescription(this OpenApiSchema schema, bool includeDescriptionFromAttribute = false)
{
if (schema.Enum == null || schema.Enum.Count == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal class XEnumNamesParameterFilter : IParameterFilter
#region Fields

private readonly bool _includeXEnumDescriptions;
private readonly bool _includeXEnumRemarks;
private readonly DescriptionSources _descriptionSources;
private readonly bool _applyFiler;
private readonly HashSet<XPathNavigator> _xmlNavigators = new HashSet<XPathNavigator>();
Expand All @@ -36,6 +37,7 @@ public XEnumNamesParameterFilter(IOptions<FixEnumsOptions> options, Action<FixEn
{
configureOptions?.Invoke(options.Value);
this._includeXEnumDescriptions = options.Value?.IncludeDescriptions ?? false;
this._includeXEnumRemarks = options.Value?.IncludeXEnumRemarks ?? false;
this._descriptionSources = options.Value?.DescriptionSource ?? DescriptionSources.DescriptionAttributes;
this._applyFiler = options.Value?.ApplyParameterFilter ?? false;
foreach (var filePath in options.Value?.IncludedXmlCommentsPaths)
Expand Down Expand Up @@ -79,7 +81,7 @@ public void Apply(OpenApiParameter parameter, ParameterFilterContext context)

if (this._includeXEnumDescriptions)
{
enumsDescriptionsArray.AddRange(EnumTypeExtensions.GetEnumValuesDescription(typeInfo, this._descriptionSources, this._xmlNavigators));
enumsDescriptionsArray.AddRange(EnumTypeExtensions.GetEnumValuesDescription(typeInfo, this._descriptionSources, this._xmlNavigators, this._includeXEnumRemarks));
if (!parameter.Extensions.ContainsKey("x-enumDescriptions") && enumsDescriptionsArray.Any())
{
parameter.Extensions.Add("x-enumDescriptions", enumsDescriptionsArray);
Expand All @@ -101,7 +103,7 @@ public void Apply(OpenApiParameter parameter, ParameterFilterContext context)

if (this._includeXEnumDescriptions)
{
enumsDescriptionsArray.AddRange(EnumTypeExtensions.GetEnumValuesDescription(genericArgumentType, this._descriptionSources, this._xmlNavigators));
enumsDescriptionsArray.AddRange(EnumTypeExtensions.GetEnumValuesDescription(genericArgumentType, this._descriptionSources, this._xmlNavigators, this._includeXEnumRemarks));
if (!parameter.Extensions.ContainsKey("x-enumDescriptions") && enumsDescriptionsArray.Any())
{
parameter.Extensions.Add("x-enumDescriptions", enumsDescriptionsArray);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal class XEnumNamesSchemaFilter : ISchemaFilter
#region Fields

private readonly bool _includeXEnumDescriptions;
private readonly bool _includeXEnumRemarks;
private readonly DescriptionSources _descriptionSources;
private readonly bool _applyFiler;
private readonly HashSet<XPathNavigator> _xmlNavigators = new HashSet<XPathNavigator>();
Expand All @@ -37,6 +38,7 @@ public XEnumNamesSchemaFilter(IOptions<FixEnumsOptions> options, Action<FixEnums
{
configureOptions?.Invoke(options.Value);
this._includeXEnumDescriptions = options.Value.IncludeDescriptions;
this._includeXEnumRemarks = options.Value.IncludeXEnumRemarks;
this._descriptionSources = options.Value?.DescriptionSource ?? DescriptionSources.DescriptionAttributes;
this._applyFiler = options.Value.ApplySchemaFilter;
foreach (var filePath in options.Value.IncludedXmlCommentsPaths)
Expand Down Expand Up @@ -77,7 +79,7 @@ public void Apply(OpenApiSchema schema, SchemaFilterContext context)

if (this._includeXEnumDescriptions)
{
enumsDescriptionsArray.AddRange(EnumTypeExtensions.GetEnumValuesDescription(context.Type, this._descriptionSources, this._xmlNavigators));
enumsDescriptionsArray.AddRange(EnumTypeExtensions.GetEnumValuesDescription(context.Type, this._descriptionSources, this._xmlNavigators, this._includeXEnumRemarks));
if (!schema.Extensions.ContainsKey("x-enumDescriptions") && enumsDescriptionsArray.Any())
{
schema.Extensions.Add("x-enumDescriptions", enumsDescriptionsArray);
Expand Down Expand Up @@ -110,7 +112,7 @@ public void Apply(OpenApiSchema schema, SchemaFilterContext context)

if (this._includeXEnumDescriptions)
{
enumsDescriptionsArray.AddRange(EnumTypeExtensions.GetEnumValuesDescription(genericArgumentType, this._descriptionSources, this._xmlNavigators));
enumsDescriptionsArray.AddRange(EnumTypeExtensions.GetEnumValuesDescription(genericArgumentType, this._descriptionSources, this._xmlNavigators, this._includeXEnumRemarks));
if (!schemaPropertyValue.Extensions.ContainsKey("x-enumDescriptions") && enumsDescriptionsArray.Any())
{
schemaPropertyValue.Extensions.Add("x-enumDescriptions", enumsDescriptionsArray);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public class FixEnumsOptions
/// </summary>
public bool IncludeDescriptions { get; set; } = false;

/// <summary>
/// Include remarks for descriptions from xml comments. Default value is false.
/// </summary>
public bool IncludeXEnumRemarks { get; set; } = false;

/// <summary>
/// Source to get descriptions. Default value is <see cref="DescriptionSources.DescriptionAttributes"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
<NeutralLanguage></NeutralLanguage>
<LangVersion>7.3</LangVersion>
<PackageIconUrl>https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions/blob/master/assets/icon.png?raw=true</PackageIconUrl>
<Version>2.3.12</Version>
<AssemblyVersion>2.3.12.0</AssemblyVersion>
<FileVersion>2.3.12.0</FileVersion>
<Version>2.3.13</Version>
<AssemblyVersion>2.3.13.0</AssemblyVersion>
<FileVersion>2.3.13.0</FileVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<DocumentationFile>Unchase.Swashbuckle.AspNetCore.Extensions.xml</DocumentationFile>
</PropertyGroup>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions test/WebApi3.1-Swashbuckle/Models/ComplicatedClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,22 @@ public class ComplicatedClass
/// <summary>
/// Tag.
/// </summary>
/// <remarks>
/// Tag remarks.
/// </remarks>
public Tag Tag { get; set; }

/// <summary>
/// Inner class.
/// </summary>
/// <remarks>
/// <para>
/// InnerClass remarks.
/// </para>
/// <para>
/// With para.
/// </para>
/// </remarks>
public InnerClass InnerClass { get; set; }
}

Expand Down
5 changes: 4 additions & 1 deletion test/WebApi3.1-Swashbuckle/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void ConfigureServices(IServiceCollection services)

// Add filters to fix enums
// use by default:
//options.AddEnumsWithValuesFixFilters();
options.AddEnumsWithValuesFixFilters();

// or configured:
options.AddEnumsWithValuesFixFilters(services, o =>
Expand All @@ -59,6 +59,9 @@ public void ConfigureServices(IServiceCollection services)
// add descriptions from DescriptionAttribute or xml-comments to fix enums (add 'x-enumDescriptions' for schema extensions) for applied filters
o.IncludeDescriptions = true;

// add remarks for descriptions from xml-comments
o.IncludeXEnumRemarks = true;

// get descriptions from DescriptionAttribute then from xml-comments
o.DescriptionSource = DescriptionSources.DescriptionAttributesThenXmlComments;

Expand Down

0 comments on commit 970a850

Please sign in to comment.