Skip to content

Commit

Permalink
Add "ChangeAllResponsesByHttpStatusCode<T>" extension method for "Swa…
Browse files Browse the repository at this point in the history
…ggerGenOptions" allows to change all responses by specific http status codes in OpenApi document;

Add "TagOrderByNameDocumentFilter" for ordering tags by name in OpenApi document.
  • Loading branch information
Chebotov Nikolay committed Feb 19, 2020
1 parent 88365b7 commit 6a403de
Show file tree
Hide file tree
Showing 12 changed files with 394 additions and 22 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.1.0 `(2020-02-19)`

- [x] Add `ChangeAllResponsesByHttpStatusCode<T>` extension method for `SwaggerGenOptions` allows to change all responses by specific http status codes in OpenApi document
- [x] Add `TagOrderByNameDocumentFilter` for ordering tags by name in OpenApi document

## v2.0.0 `(2020-02-08)`

**BREAKING CHANGES (see [README.md](README.md)):**
Expand Down
76 changes: 71 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ app.UseSwaggerUI(c =>
});
```

1. **Fix enums**:
1. **Fix enums in OpenApi document**:
- In the _ConfigureServices_ method of _Startup.cs_, inside your `AddSwaggerGen` call, enable whichever extensions (filters) you need:

```csharp
Expand Down Expand Up @@ -108,7 +108,7 @@ public void ConfigureServices(IServiceCollection services)
}
```

2. **Hide Paths and Defenitions from OpenApi documentation** without accepted roles:
2. **Hide Paths and Defenitions from OpenApi documentation** without accepted roles in OpenApi document:
- In the _ConfigureServices_ method of _Startup.cs_, inside your `AddSwaggerGen` call, enable `HidePathsAndDefinitionsByRolesDocumentFilter` document filter:

```csharp
Expand All @@ -127,7 +127,7 @@ public void ConfigureServices(IServiceCollection services)
}
```

3. **Append action count into the SwaggetTag's descriptions**:
3. **Append action count into the SwaggetTag's descriptions in OpenApi document**:
- In the _ConfigureServices_ method of _Startup.cs_, inside your `AddSwaggerGen` call, enable `AppendActionCountToTagSummaryDocumentFilter` document filter:

```csharp
Expand Down Expand Up @@ -164,6 +164,66 @@ public class TodoController : ControllerBase
...
```

4. **Change all responses for specific HTTP status codes in OpenApi document**:
- In the _ConfigureServices_ method of _Startup.cs_, inside your `AddSwaggerGen` call, enable `ChangeAllResponsesByHttpStatusCode<T>` extension (filter) with whichever HTTP status codes you need:

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

services.AddSwaggerGen(options =>
{
...

// change responses for specific HTTP status code ("200")
options.ChangeAllResponsesByHttpStatusCode(
httpStatusCode: 200,
responseDescription: "200 status code description",
responseExampleOption : ResponseExampleOptions.AddNew, // add new response examples
responseExample: new TodoItem { Tag = Tag.Workout, Id = 111, IsComplete = false, Name = "test" }); // some class for response examples
// change responses for specific HTTP status code ("400" (HttpStatusCode.BadRequest))
options.ChangeAllResponsesByHttpStatusCode(
httpStatusCode: HttpStatusCode.BadRequest,
responseDescription: "400 status code description",
responseExampleOption: ResponseExampleOptions.Clear, // claer response examples
responseExample: new ComplicatedClass()); // some class for response examples
// change responses for specific HTTP status code ("201" (StatusCodes.Status201Created))
options.ChangeAllResponsesByHttpStatusCode(
httpStatusCode: StatusCodes.Status201Created,
responseDescription: "201 status code description",
responseExampleOption: ResponseExampleOptions.None, // do nothing with response examples
responseExample: new ComplicatedClass()); // some class for response examples
...
});
}
```

5. **Order tags by name in OpenApi document**:
- In the _ConfigureServices_ method of _Startup.cs_, inside your `AddSwaggerGen` call, enable `TagOrderByNameDocumentFilter` document filter:

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

services.AddSwaggerGen(options =>
{
...

// order tags by name
options.DocumentFilter<TagOrderByNameDocumentFilter>();

...
});
}
```

## Builds status

|Status|Value|
Expand All @@ -179,7 +239,7 @@ public class TodoController : ControllerBase

## Features

#### Fix enums
### Fix enums

- Add an output enums integer values with there strings like `0 = FirstEnumValue` without a `StringEnumConverter` in swaggerUI and schema (by default enums will output only their integer values)
- Add description to each enum value that has an `[Description]` attribute in `swaggerUI` and schema - should use *options.DocumentFilter\<DisplayEnumsWithValuesDocumentFilter\>(**true**);* or *options.AddEnumsWithValuesFixFilters(**true**);*
Expand Down Expand Up @@ -275,7 +335,7 @@ public class TodoController : ControllerBase
}
```

#### Hide Paths and Defenitions from OpenApi documentation
### Hide Paths and Defenitions from OpenApi documentation

- Hide all OpenAPIDocument **Paths** and **Defenitions** without accepted roles:

Expand Down Expand Up @@ -319,6 +379,12 @@ public class SamplePersonController : ControllerBase
}
```

### Change responses for specific HTTP status codes

For example:

![Change responses](assets/changeResponsesByHttpStatusCode.png)

## HowTos

- [ ] Add HowTos in a future
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.0.{build}
version: 2.1.{build}
pull_requests:
do_not_increment_build_number: true
skip_tags: true
Expand Down
Binary file added assets/changeResponsesByHttpStatusCode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,28 @@
using System;
using System.Text;
using System.Collections.Generic;
using System.ComponentModel;

namespace Unchase.Swashbuckle.AspNetCore.Extensions.Extensions
{
internal static class EnumTypeExtensions
{
internal static string GetDescriptionFromEnumOption(Type enumOptionType, object enumOption)
private static string GetDescriptionFromEnumOption(Type enumOptionType, object enumOption)
{
return enumOptionType.GetFieldAttributeDescription(enumOption, 0);
}

private static string GetFieldAttributeDescription(this Type enumType, object enumField, int attributeNumber)
{
if (!enumType.IsEnum)
return string.Empty;
var memInfo = enumType.GetMember(enumField.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
return (attributes[attributeNumber] as DescriptionAttribute)?.Description ?? string.Empty;
return string.Empty;
}

internal static List<OpenApiString> GetEnumValuesDescription(Type enumType)
{
var enumsDescriptions = new List<OpenApiString>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System;
using System.ComponentModel;
using System.ComponentModel;
using System.Net;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.SwaggerGen;
using Unchase.Swashbuckle.AspNetCore.Extensions.Filters;
Expand All @@ -8,22 +8,69 @@ namespace Unchase.Swashbuckle.AspNetCore.Extensions.Extensions
{
public static class SwaggerGenOptionsExtensions
{
public static void AddEnumsWithValuesFixFilters(this SwaggerGenOptions swaggerGenOptions, bool includeDescriptionFromAttribute = false)
#region Extensions

/// <summary>
/// Change all responses by specific http status codes in OpenApi document.
/// </summary>
/// <typeparam name="T">Type of response example.</typeparam>
/// <param name="swaggerGenOptions"><see cref="SwaggerGenOptions"/>.</param>
/// <param name="httpStatusCode">HTTP status code.</param>
/// <param name="responseDescription">Response description.</param>
/// <param name="responseExampleOption"><see cref="ResponseExampleOptions"/>.</param>
/// <param name="responseExample">New example for response.</param>
/// <returns>
/// Returns <see cref="SwaggerGenOptions"/>.
/// </returns>
public static SwaggerGenOptions ChangeAllResponsesByHttpStatusCode<T>(
this SwaggerGenOptions swaggerGenOptions,
int httpStatusCode,
string responseDescription = null,
ResponseExampleOptions responseExampleOption = ResponseExampleOptions.None,
T responseExample = default) where T : class
{
swaggerGenOptions.DocumentFilter<ChangeResponseByHttpStatusCodeDocumentFilter<T>>(httpStatusCode, responseDescription, responseExampleOption, responseExample);
return swaggerGenOptions;
}

/// <summary>
/// Change all responses by specific http status codes in OpenApi document.
/// </summary>
/// <typeparam name="T">Type of response example.</typeparam>
/// <param name="swaggerGenOptions"><see cref="SwaggerGenOptions"/>.</param>
/// <param name="httpStatusCode">HTTP status code.</param>
/// <param name="responseDescription">Response description.</param>
/// <param name="responseExampleOption"><see cref="ResponseExampleOptions"/>.</param>
/// <param name="responseExample">New example for response.</param>
/// <returns>
/// Returns <see cref="SwaggerGenOptions"/>.
/// </returns>
public static SwaggerGenOptions ChangeAllResponsesByHttpStatusCode<T>(
this SwaggerGenOptions swaggerGenOptions,
HttpStatusCode httpStatusCode,
string responseDescription = null,
ResponseExampleOptions responseExampleOption = ResponseExampleOptions.None,
T responseExample = default) where T : class
{
return swaggerGenOptions.ChangeAllResponsesByHttpStatusCode((int)httpStatusCode, responseDescription, responseExampleOption, responseExample);
}

/// <summary>
/// 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)
{
swaggerGenOptions.SchemaFilter<XEnumNamesSchemaFilter>(includeDescriptionFromAttribute);
swaggerGenOptions.ParameterFilter<XEnumNamesParameterFilter>(includeDescriptionFromAttribute);
swaggerGenOptions.DocumentFilter<DisplayEnumsWithValuesDocumentFilter>(includeDescriptionFromAttribute);
return swaggerGenOptions;
}

internal static string GetFieldAttributeDescription(this Type enumType, object enumField, int attributeNumber)
{
if (!enumType.IsEnum)
return string.Empty;
var memInfo = enumType.GetMember(enumField.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
return (attributes[attributeNumber] as DescriptionAttribute)?.Description ?? string.Empty;
return string.Empty;
}
#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Linq;

namespace Unchase.Swashbuckle.AspNetCore.Extensions.Filters
{
/// <summary>
/// Options for response example.
/// </summary>
public enum ResponseExampleOptions
{
/// <summary>
/// Clear example.
/// </summary>
Clear = 0,

/// <summary>
/// Add (replace) example.
/// </summary>
AddNew = 1,

/// <summary>
/// Do nothing.
/// </summary>
None = 2
}

/// <summary>
/// Document filter for changing responses by specific http status codes in OpenApi document.
/// </summary>
/// <typeparam name="T">Type of response example.</typeparam>
internal class ChangeResponseByHttpStatusCodeDocumentFilter<T> : IDocumentFilter where T : class
{
#region Fileds

private int _httpStatusCode;

private string _responseDescription;

private ResponseExampleOptions _responseExampleOption;

private T _responseExample;

#endregion

#region Constructors

/// <summary>
/// Constructor.
/// </summary>
/// <param name="httpStatusCode">HTTP status code.</param>
/// <param name="responseDescription">Response description.</param>
/// <param name="responseExampleOption"><see cref="ResponseExampleOptions"/>.</param>
/// <param name="responseExample">New example for response.</param>
public ChangeResponseByHttpStatusCodeDocumentFilter(int httpStatusCode, string responseDescription, ResponseExampleOptions responseExampleOption, T responseExample)
{
this._httpStatusCode = httpStatusCode;
this._responseDescription = responseDescription;
this._responseExampleOption = responseExampleOption;
this._responseExample = responseExample;
}

#endregion

#region Methods

/// <summary>
/// Apply filter.
/// </summary>
/// <param name="swaggerDoc"><see cref="OpenApiDocument"/>.</param>
/// <param name="context"><see cref="DocumentFilterContext"/>.</param>
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
if (context.SchemaRepository.Schemas.ContainsKey(typeof(T).Name))
{
var schema = context.SchemaRepository.Schemas[typeof(T).Name];
foreach (var response in swaggerDoc.Paths.SelectMany(p => p.Value.Operations.SelectMany(o => o.Value.Responses)))
{
if (response.Key == this._httpStatusCode.ToString())
{
if (!string.IsNullOrWhiteSpace(this._responseDescription))
response.Value.Description = this._responseDescription;

if (response.Value.Content.ContainsKey("application/json"))
{
var jsonContent = response.Value.Content["application/json"];
switch (this._responseExampleOption)
{
case ResponseExampleOptions.Clear:
response.Value.Content.Remove("application/json");
break;
case ResponseExampleOptions.AddNew:
if (this._responseExample != null)
jsonContent.Example = new OpenApiString(System.Text.Json.JsonSerializer.Serialize(this._responseExample, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));
jsonContent.Schema = schema;
break;
case ResponseExampleOptions.None:
default:
break;
}
}
else
{
switch (this._responseExampleOption)
{
case ResponseExampleOptions.AddNew:
if (this._responseExample != null)
{
response.Value.Content.Add("application/json", new OpenApiMediaType()
{
Example = new OpenApiString(System.Text.Json.JsonSerializer.Serialize(this._responseExample, new System.Text.Json.JsonSerializerOptions { WriteIndented = true })),
Schema = schema
});
}
break;
case ResponseExampleOptions.Clear:
case ResponseExampleOptions.None:
default:
break;
}

}
}
}
}
}

#endregion
}
}
Loading

0 comments on commit 6a403de

Please sign in to comment.