Swashbuckle.AspNetCore 5.0.0-rc4
Create controller that accepts get with 'FromQuery' object that contains enum.
Controller method:
```c#
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))]
public Task
{
return Task.FromResult((ActionResult
}
Object:
```c#
public class JustFilter
{
public string JustName { get; set; }
public JustAnotherEnum? JustEnum { get; set; }
}
Enum:
c#
[JsonConverter(typeof(StringEnumConverter))]
public enum JustAnotherEnum
{
A = 0,
B = 1,
C = 2,
D = 10,
E = 11,
}
Configure app.UseSwagger(o => o.SerializeAsV2 = true);
/swagger/v1/swagger.json Valid Swagger file with emus defined for query parameter.
Enum type is missing (look at JustEnum in the JSON below), but it is generated in definitions.
{
"swagger": "2.0",
"info": {
"title": "Just an API",
"version": "v1"
},
"paths": {
"/Just": {
"get": {
"tags": [
"Just"
],
"produces": [
"text/plain",
"application/json",
"text/json"
],
"parameters": [
{
"in": "query",
"name": "JustName",
"type": "string"
},
{
"in": "query",
"name": "JustEnum"
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
"JustAnotherEnum": {
"enum": [
"A",
"B",
"C",
"D",
"E"
],
"type": "string"
}
}
}
This looks to be an issue with the Microsoft.OpenAPI library. Swashbuckle leverages the Swagger/OpenAPI object model and serialization to JSON behavior from that library. As the V3 output is correct, that would indicate an issue with the dependency, specifically it's serialize to V2 mode, and not Swahbuckle itself:
// Write V2 as JSON (Snippet from the Micorosft.OpenAPI readme
var outputString = openApiDocument.Serialize(OpenApiSpecVersion.OpenApi2_0, OpenApiFormat.Json);
You should be able to repro by playing with the above snippet, independently of Swashbuckle. Could you please do this and then submit a corresponding issue to that project?
Sidenote - 馃憤 for the issue format that you've posted here. I wish all issue were crafted with this level of detail.
You should be able to repro by playing with the above snippet, independently of Swashbuckle. Could you please do this and then submit a corresponding issue to that project?
Here is the OpenAPI.NET (Version=1.1.4.0) only code that presumably should reproduce this issue, but it seems to be generating correct Swagger (I modified their example and tried to mimic the way enums are generated in Swashbuckle):
```C#
var json = new OpenApiDocument
{
Info = new OpenApiInfo
{
Version = "1.0.0",
Title = "Just a Swagger ",
},
Servers = new List
{
new OpenApiServer { Url = "http://just.swagger.io/api" }
},
Paths = new OpenApiPaths
{
["/justCall"] = new OpenApiPathItem
{
Operations = new Dictionary
{
[OperationType.Get] = new OpenApiOperation
{
Description = "Just returns",
Parameters =
{
new OpenApiParameter()
{
In = ParameterLocation.Query,
Name = "justString",
Schema = new OpenApiSchema
{
Type = "string",
}
},
new OpenApiParameter()
{
In = ParameterLocation.Query,
Name = "justEnum",
Schema = new OpenApiSchema
{
Type = "string",
Enum = typeof(JustAnotherEnum)
.GetEnumValues()
.Cast
```json
{
"swagger": "2.0",
"info": {
"title": "Just a Swagger ",
"version": "1.0.0"
},
"host": "just.swagger.io",
"basePath": "/api",
"schemes": [
"http"
],
"paths": {
"/justCall": {
"get": {
"description": "Just returns",
"parameters": [
{
"in": "query",
"name": "justString",
"type": "string"
},
{
"in": "query",
"name": "justEnum",
"type": "string",
"enum": [
"A",
"B",
"C",
"D",
"E"
]
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
Let me know if you see any obvious issues with the example above. It seems so far that OpenAPI.NET is capable of generating correct Swagger. I wonder if there might be some edge case beyond simple API definition that might trigger this issue.
I debugged it a little and it looks like by the time JSON gets generated schema of the enum parameter contains reference to the type definition rather than the definition itself. I am not sure what the expected behavior should be here. Indirectly related to another issue #1160
@domaindrivendev do you still think that the root cause belongs to OpenAPI.NET in this case?
Thanks for putting the repro together and posting that. Let's wait and see what the response is but IMO, the v3 Swagger being generated by Swashbuckle is perfectly valid despite choosing references over inline schemas for enum definitions. So, I do believe the issue still lies with OpenAPI.NET
In the meantime, I have a manual workaround that might help (although it's not particularly pretty):
When configuring Swagger, you can apply custom type-to-schema mappings and can use this to manually provide an "inline" schema for a given enum type. Of course the downside to this is you would have to do it for every enum defined in your project. But it is a workaround:
c.MapType<JustAnotherEnum?>(() => new OpenApiSchema
{
Type = "string",
Enum = typeof(JustAnotherEnum).GetEnumNames().Select(name => new OpenApiString(name)).Cast<IOpenApiAny>().ToList(),
Nullable = true
});
Just ran into this myself... At first glance, it looks like this might be the issue:
if (apiModel is ApiPrimitive apiPrimitive)
{
shouldBeReferenced = apiPrimitive.IsEnum;
return true;
}
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/14f3444536c480a0c1608b042fffc3aea16ef39f/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/ApiPrimitiveHandler.cs#L17
A V2 spec should never try to build enums as refs, right?
This is also happening to me in version Swashbuckle.AspNetCore 5.0.0-rc4, it wasn't happening in release Swashbuckle.AspNetCore 5.0.0-rc2.
I used the proposed workaround from @domaindrivendev and it's working, although it's not the ideal solution, just a temporary fix.
I implemented a more generic method, to register all enums from a namespace
https://gist.github.com/joaopi/5bd57a285662930111dc289e78ff6743
using System;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Example
{
public static class SwaggerGenOptionsExtensions
{
public static SwaggerGenOptions RegisterEnumSchemas(this SwaggerGenOptions options, Assembly assembly, string enumsNamespace)
{
var enums = from t in assembly.GetTypes()
where t.IsEnum && t.Namespace == enumsNamespace
select t;
foreach (var enumerate in enums)
{
var nullableEnumerate = typeof(Nullable<>).MakeGenericType(enumerate);
MapEnumType(options, enumerate, false);
MapEnumType(options, nullableEnumerate, true);
}
return options;
}
private static void MapEnumType(SwaggerGenOptions options, Type enumerate, bool nullable)
{
var underlyingEnum = nullable ? Nullable.GetUnderlyingType(enumerate) : enumerate;
options.MapType(enumerate, () => new OpenApiSchema
{
Type = "string",
Enum = underlyingEnum.GetEnumNames().Select(name => new OpenApiString(name)).Cast<IOpenApiAny>().ToList(),
Nullable = nullable
});
}
}
}
@j-peacock I noticed that part myself as well. That was the behavior with swashbuckle 4.X packages at least.
Another thing to keep in mind it also affects arrays. Rough workaround to just make it work for enums and arrays is to create a schema filter that in-lines schema:
c#
public class SchemaFilter : IParameterFilter
{
public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
{
if (parameter.In.HasValue && parameter.In.Value == ParameterLocation.Query)
{
if (parameter.Schema != null && parameter.Schema.Reference != null)
{
parameter.Schema = context.SchemaRepository.Schemas[parameter.Schema.Reference.Id];
}
if (parameter.Schema != null && parameter.Schema.Items != null && parameter.Schema.Items.Reference != null)
{
parameter.Schema.Items = context.SchemaRepository.Schemas[parameter.Schema.Items.Reference.Id];
}
}
}
}
I just loop through the all assemblies and register the enums.
foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type t in a.GetTypes())
{
if (t.IsEnum)
{
options.MapType(t, () => new OpenApiSchema
{
Type = "string",
Enum = t.GetEnumNames().Select(name => new OpenApiString(name)).Cast<IOpenApiAny>().ToList(),
Nullable = true
});
}
}
}
@pechkarus you may find the changes introduced by 9abdbf99a271de5decb2bd93c5c614632e750b32 of interest. Specifically, it adds an option to generate enum schema's inline as opposed to referncing a shared definition (the new default behavior). You _should_ be able to use this setting (i.e. c.UseInlineDefinitionsForEnums) as a workaround to the issue described here.
You _should_ be able to use this setting (i.e.
c.UseInlineDefinitionsForEnums) as a workaround to the issue described here.
With UseInlineDefinitionsForEnums it generates definition inline but inserts description property inside items
"parameters": [
{
"in": "query",
"name": "arrayofenums",
"description": "Array containing enums",
"required": true,
"type": "array",
"items": {
"description": "Defines values for TestEnum.",
"enum": [
"TestEnumValue1",
"TestEnumValue2",
"TestEnumValue3"
],
"type": "string"
}
}
],
and this is reported as structural error by editor.swagger.io:
Structural error at paths./test.get.parameters.0.items
should NOT have additional properties
additionalProperty: description
I'm closing this as the new setting UseInlineDefinitionsForEnums resolves the original issue.
@older - could you please create a separate issue for the structural problem you've identified. Thanks
Most helpful comment
@pechkarus you may find the changes introduced by 9abdbf99a271de5decb2bd93c5c614632e750b32 of interest. Specifically, it adds an option to generate enum schema's inline as opposed to referncing a shared definition (the new default behavior). You _should_ be able to use this setting (i.e.
c.UseInlineDefinitionsForEnums) as a workaround to the issue described here.