Skip to content

Commit

Permalink
1) Add feature: add configured options to "AddEnumsWithValuesFixFilte…
Browse files Browse the repository at this point in the history
…rs";

2) Fix bug: close #7 - get descriptions from xml comments;
3) Fix small bugs.
  • Loading branch information
unchase committed Mar 22, 2020
1 parent 5744333 commit 18c8159
Show file tree
Hide file tree
Showing 13 changed files with 412 additions and 70 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@

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.0 `(2020-03-23)`

- [x] Add feature: add configured options to `AddEnumsWithValuesFixFilters`
- [x] Fix bug: fix #7 - get descriptions from xml comments
- [x] Fix small bugs

## v2.2.6 `(2020-03-22)`

- [x] Fix bug: add fix #6 to `options.AddEnumsWithValuesFixFilters(true);` when using `JsonStringEnumConverter()`
Expand Down
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@
## Breaking Changes

Since [v2.0.0](https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions/releases/tag/v2.0.0) supports [Swashbuckle.AspNetCore 5.0.0](https://www.nuget.org/packages/Swashbuckle.AspNetCore/) with **breaking changes**.
* Since [v2.0.0](https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions/releases/tag/v2.0.0) supports [Swashbuckle.AspNetCore 5.0.0](https://www.nuget.org/packages/Swashbuckle.AspNetCore/) with **breaking changes**.

For old versions see [README_OLD.md](README_OLD.md).

* Since [v2.3.0](https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions/releases/tag/v2.3.0) there are [**breaking changes**](#breaking-changes-2.3.0) in `Startup.cs`:

```csharp

```


### Compatibility

|Swashbuckle Version|ASP.NET Core|Swagger / OpenAPI Spec.|This extension Version|
Expand Down Expand Up @@ -82,6 +89,54 @@ using Unchase.Swashbuckle.AspNetCore.Extensions.Extensions;
using Unchase.Swashbuckle.AspNetCore.Extensions.Filters;
```

* <a name="breaking-changes-2.3.0"></a> Since [v2.3.0](https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions/releases/tag/v2.3.0):

```csharp
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddControllers();

// Register the Swagger generator
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });

// if you want to add xml comments into the swagger documentation, first of all add:
var xmlFilePath = Path.Combine(AppContext.BaseDirectory, "WebApi3.1-Swashbuckle.xml");
options.IncludeXmlComments(xmlFilePath);

// Add filters to fix enums
// use by default:
//options.AddEnumsWithValuesFixFilters();
// or configured:
options.AddEnumsWithValuesFixFilters(services, o =>
{
// add schema filter to fix enums (add 'x-enumNames' for NSwag) in schema
o.ApplySchemaFilter = true;

// add parameter filter to fix enums (add 'x-enumNames' for NSwag) in schema parameters
o.ApplyParameterFilter = true;

// add document filter to fix enums displaying in swagger document
o.ApplyDocumentFilter = true;

// add descriptions from DescriptionAttribute or xml-comments to fix enums (add 'x-enumDescriptions' for schema extensions) for applied filters
o.IncludeDescriptions = true;

// get descriptions from xml-file comments on the specified path
// should use "options.IncludeXmlComments(xmlFilePath);" before
o.IncludeXmlCommentsFrom(xmlFilePath);
// the same for another xml-files...
});
});
}
```

* For older versions:

```csharp
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
Expand Down Expand Up @@ -354,8 +409,11 @@ public void ConfigureServices(IServiceCollection services)
```csharp
/// <summary>Sample Person title.
///
/// 0 = None (None enum description)
///
/// 1 = Miss (Miss enum description)
///
/// 2 = Mr (Mr enum description)</summary>
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.13.35.0 (Newtonsoft.Json v11.0.0.0)")]
public enum Title
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 2.2.{build}
version: 2.3.{build}
pull_requests:
do_not_increment_build_number: true
skip_tags: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
using System.Text;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Xml.XPath;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace Unchase.Swashbuckle.AspNetCore.Extensions.Extensions
{
Expand All @@ -25,17 +29,38 @@ private static string GetFieldAttributeDescription(this Type enumType, object en
return string.Empty;
}

internal static List<OpenApiString> GetEnumValuesDescription(Type enumType)
internal static List<OpenApiString> GetEnumValuesDescription(Type enumType, IEnumerable<XPathNavigator> xmlNavigators = null)
{
var enumsDescriptions = new List<OpenApiString>();
foreach (var enumValue in Enum.GetValues(enumType))
{
var enumDescription = GetDescriptionFromEnumOption(enumType, enumValue);
if (string.IsNullOrWhiteSpace(enumDescription))
{
var memberInfo = enumType.GetMembers().FirstOrDefault(m => m.Name.Equals(enumValue.ToString(), StringComparison.InvariantCultureIgnoreCase));
enumDescription = TryGetMemberComments(memberInfo, xmlNavigators);
}
enumsDescriptions.Add(new OpenApiString(enumDescription));
}
return enumsDescriptions;
}

private static string TryGetMemberComments(MemberInfo memberInfo, IEnumerable<XPathNavigator> xmlNavigators)
{
if (xmlNavigators == null)
return string.Empty;

foreach (var xmlNavigator in xmlNavigators)
{
var xpathNavigator1 = xmlNavigator.SelectSingleNode(
$"/doc/members/member[@name='{XmlCommentsNodeNameHelper.GetNodeNameForMember(memberInfo)}']");
var xpathNavigator2 = xpathNavigator1?.SelectSingleNode("summary");
return xpathNavigator2 != null ? XmlCommentsTextHelper.Humanize(xpathNavigator2.InnerXml) : string.Empty;
}

return string.Empty;
}

internal static string AddEnumValuesDescription(this OpenApiSchema schema, bool includeDescriptionFromAttribute = false)
{
if (schema.Enum == null || schema.Enum.Count == 0)
Expand All @@ -45,7 +70,7 @@ internal static string AddEnumValuesDescription(this OpenApiSchema schema, bool
return null;

var sb = new StringBuilder();
for (int i = 0; i < schema.Enum.Count; i++)
for (var i = 0; i < schema.Enum.Count; i++)
{
if (schema.Enum[i] is OpenApiInteger schemaEnumInt)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System.ComponentModel;
using System;
using System.Net;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.SwaggerGen;
using Unchase.Swashbuckle.AspNetCore.Extensions.Filters;
using Unchase.Swashbuckle.AspNetCore.Extensions.Options;

namespace Unchase.Swashbuckle.AspNetCore.Extensions.Extensions
{
Expand All @@ -26,7 +27,7 @@ public static class SwaggerGenOptionsExtensions
/// Returns <see cref="SwaggerGenOptions"/>.
/// </returns>
public static SwaggerGenOptions ChangeAllResponsesByHttpStatusCode<T>(
this SwaggerGenOptions swaggerGenOptions,
this SwaggerGenOptions swaggerGenOptions,
int httpStatusCode,
string responseDescription = null,
ResponseExampleOptions responseExampleOption = ResponseExampleOptions.None,
Expand Down Expand Up @@ -62,18 +63,25 @@ public static SwaggerGenOptions ChangeAllResponsesByHttpStatusCode<T>(
/// Add filters to fix enums in OpenApi document.
/// </summary>
/// <param name="swaggerGenOptions"><see cref="SwaggerGenOptions"/>.</param>
/// <param name="includeDescriptionFromAttribute">If true - add extensions (descriptions) from <see cref="DescriptionAttribute"/>.</param>
/// <returns>
/// Returns <see cref="SwaggerGenOptions"/>.
/// </returns>
public static SwaggerGenOptions AddEnumsWithValuesFixFilters(this SwaggerGenOptions swaggerGenOptions, bool includeDescriptionFromAttribute = false)
/// <param name="services"><see cref="IServiceCollection"/>.</param>
/// <param name="configureOptions">An <see cref="Action{FixEnumsOptions}"/> to configure options for filters.</param>
/// <returns></returns>
public static SwaggerGenOptions AddEnumsWithValuesFixFilters(this SwaggerGenOptions swaggerGenOptions, IServiceCollection services = null, Action<FixEnumsOptions> configureOptions = null)
{
swaggerGenOptions.SchemaFilter<XEnumNamesSchemaFilter>(includeDescriptionFromAttribute);
swaggerGenOptions.ParameterFilter<XEnumNamesParameterFilter>(includeDescriptionFromAttribute);
swaggerGenOptions.DocumentFilter<DisplayEnumsWithValuesDocumentFilter>(includeDescriptionFromAttribute);
// local function
void EmptyAction(FixEnumsOptions x) { }

if (configureOptions != null)
{
services?.Configure(configureOptions);
}

swaggerGenOptions.SchemaFilter<XEnumNamesSchemaFilter>(configureOptions ?? EmptyAction);
swaggerGenOptions.ParameterFilter<XEnumNamesParameterFilter>(configureOptions ?? EmptyAction);
swaggerGenOptions.DocumentFilter<DisplayEnumsWithValuesDocumentFilter>();
return swaggerGenOptions;
}

#endregion
}
}
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,59 @@
using System.Linq;
using System;
using System.Linq;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Unchase.Swashbuckle.AspNetCore.Extensions.Extensions;
using Unchase.Swashbuckle.AspNetCore.Extensions.Options;

namespace Unchase.Swashbuckle.AspNetCore.Extensions.Filters
{
public class DisplayEnumsWithValuesDocumentFilter : IDocumentFilter
internal class DisplayEnumsWithValuesDocumentFilter : IDocumentFilter
{
#region Fields

private readonly bool _applyFiler;
private readonly bool _includeDescriptionFromAttribute;

#endregion

#region Constructors

public DisplayEnumsWithValuesDocumentFilter(bool includeDescriptionFromAttribute = false)
/// <summary>
/// Constructor.
/// </summary>
/// <param name="options"><see cref="FixEnumsOptions"/>.</param>
/// <param name="configureOptions">An <see cref="Action{FixEnumsOptions}"/> to configure options for filter.</param>
public DisplayEnumsWithValuesDocumentFilter(IOptions<FixEnumsOptions> options, Action<FixEnumsOptions> configureOptions = null)
{
_includeDescriptionFromAttribute = includeDescriptionFromAttribute;
if (options.Value != null)
{
configureOptions?.Invoke(options.Value);
this._includeDescriptionFromAttribute = options.Value.IncludeDescriptions;
this._applyFiler = options.Value.ApplyDocumentFilter;
}
}

#endregion

#region Methods

/// <summary>
/// Apply the filter.
/// </summary>
/// <param name="openApiDoc"><see cref="OpenApiDocument"/>.</param>
/// <param name="context"><see cref="DocumentFilterContext"/>.</param>
public void Apply(OpenApiDocument openApiDoc, DocumentFilterContext context)
{
if (!this._applyFiler)
return;

foreach (var schemaDictionaryItem in openApiDoc.Components.Schemas)
{
var schema = schemaDictionaryItem.Value;
schema.Description += schema.AddEnumValuesDescription(this._includeDescriptionFromAttribute);
var description = schema.AddEnumValuesDescription(this._includeDescriptionFromAttribute);
if (description != null && schema.Description != null && !schema.Description.Contains(description))
schema.Description += description;
}

if (openApiDoc.Paths.Count <= 0)
Expand All @@ -44,7 +68,9 @@ public void Apply(OpenApiDocument openApiDoc, DocumentFilterContext context)
var componentReference = parameter.Schema.Reference.Id;
var schema = openApiDoc.Components.Schemas[componentReference];

parameter.Description += schema.AddEnumValuesDescription(this._includeDescriptionFromAttribute);
var description = schema.AddEnumValuesDescription(this._includeDescriptionFromAttribute);
if (description != null && parameter.Description != null && !parameter.Description.Contains(description))
parameter.Description += description;
}

// add enum descriptions to request body
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.XPath;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Unchase.Swashbuckle.AspNetCore.Extensions.Extensions;
using Unchase.Swashbuckle.AspNetCore.Extensions.Options;

namespace Unchase.Swashbuckle.AspNetCore.Extensions.Filters
{
public class XEnumNamesParameterFilter : IParameterFilter
internal class XEnumNamesParameterFilter : IParameterFilter
{
#region Fields

private readonly bool _includeXEnumDescriptions;
private readonly bool _applyFiler;
private readonly HashSet<XPathNavigator> _xmlNavigators = new HashSet<XPathNavigator>();

#endregion

Expand All @@ -21,18 +27,39 @@ public class XEnumNamesParameterFilter : IParameterFilter
/// <summary>
/// Constructor.
/// </summary>
/// <param name="includeXEnumDescriptions">If true - add "x-enumDescriptions" extensions from <see cref="DescriptionAttribute"/>.</param>
public XEnumNamesParameterFilter(bool includeXEnumDescriptions = false)
/// <param name="options"><see cref="FixEnumsOptions"/>.</param>
/// <param name="configureOptions">An <see cref="Action{FixEnumsOptions}"/> to configure options for filter.</param>
public XEnumNamesParameterFilter(IOptions<FixEnumsOptions> options, Action<FixEnumsOptions> configureOptions = null)
{
_includeXEnumDescriptions = includeXEnumDescriptions;
if (options.Value != null)
{
configureOptions?.Invoke(options.Value);
this._includeXEnumDescriptions = options.Value?.IncludeDescriptions ?? false;
this._applyFiler = options.Value?.ApplyParameterFilter ?? false;
foreach (var filePath in options.Value?.IncludedXmlCommentsPaths)
{
if (File.Exists(filePath))
{
this._xmlNavigators.Add(new XPathDocument(filePath).CreateNavigator());
}
}
}
}

#endregion

#region Methods

/// <summary>
/// Apply the filter.
/// </summary>
/// <param name="parameter"><see cref="OpenApiParameter"/>.</param>
/// <param name="context"><see cref="ParameterFilterContext"/>.</param>
public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
{
if (!this._applyFiler)
return;

var typeInfo = context.ParameterInfo?.ParameterType ?? context.PropertyInfo.PropertyType;
var enumsArray = new OpenApiArray();
var enumsDescriptionsArray = new OpenApiArray();
Expand All @@ -47,7 +74,7 @@ public void Apply(OpenApiParameter parameter, ParameterFilterContext context)

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

if (this._includeXEnumDescriptions)
{
enumsDescriptionsArray.AddRange(EnumTypeExtensions.GetEnumValuesDescription(genericArgumentType));
enumsDescriptionsArray.AddRange(EnumTypeExtensions.GetEnumValuesDescription(genericArgumentType, this._xmlNavigators));
if (!parameter.Extensions.ContainsKey("x-enumDescriptions") && enumsDescriptionsArray.Any())
{
parameter.Extensions.Add("x-enumDescriptions", enumsDescriptionsArray);
Expand Down
Loading

0 comments on commit 18c8159

Please sign in to comment.