Reputation: 5012
Using Swashbuckle.AspNetCore v6.0.7
in an asp.net core
project net5.0
.
Let say I have models like these:
public enum MyEnum
{
A, B
}
and
public class MyModel
{
public MyEnum MyEnum { get; set; }
public MyEnum? MyEnum2 { get; set; }
}
and the swagger schema is generated like this:
"MyEnum": {
"enum": [
"A",
"B"
],
"type": "string"
},
"MyModel": {
"type": "object",
"properties": {
"myEnum": {
"$ref": "#/components/schemas/MyEnum"
},
"myEnum2": {
"$ref": "#/components/schemas/MyEnum"
}
},
"additionalProperties": false
}
As you can see, there is no difference between MyEnum
and MyEnum?
in the open-API JSON schema!
And seems nullable enum is not presented in the schema properly.
Does anyone have any idea how can I fix this?
Best
Upvotes: 11
Views: 9151
Reputation: 5012
As Yiyi You suggested, I called UseAllOfToExtendReferenceSchemas
in SwaggerGenOptions
like this:
services.AddSwaggerGen(c =>
{
c.UseAllOfToExtendReferenceSchemas();
});
and now the schema is generated like:
"MyEnum": {
"enum": [
"A",
"B"
],
"type": "string"
},
"MyModel": {
"type": "object",
"properties": {
"myEnum": {
"allOf": [
{
"$ref": "#/components/schemas/MyEnum"
}
]
},
"myEnum2": {
"allOf": [
{
"$ref": "#/components/schemas/MyEnum"
}
],
"nullable": true
}
},
"additionalProperties": false
},
and there is "nullable": true
for the type MyEnum?
.
You can find more information here.
Upvotes: 23
Reputation: 1357
The following worked well for me:
_ = services.AddSwaggerGen(c =>
{
// Enables support for nullable object properties
c.UseAllOfToExtendReferenceSchemas();
// Enable detection of non nullable reference types to set Nullable flag accordingly on schema properties
c.SupportNonNullableReferenceTypes();
})
Upvotes: 6
Reputation: 193
Solution that worked for me.
/// <summary>
/// Updates already generated schema by marking $ref enum properties in it as nullable
/// if the orginal class property is of type nullable enum type.
/// NB: schema filter can modify Schemas after they're initially generated.
/// </summary>
public class NullableEnumSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
var isReferenceType =
TypeHelper.IsReference(context.Type) &&
!TypeHelper.IsCLR(context.Type) &&
!TypeHelper.IsMicrosoft(context.Type);
if(!isReferenceType) { return; }
var bindingFlags = BindingFlags.Public | BindingFlags.Instance;
var members = context.Type.GetFields(bindingFlags).Cast<MemberInfo>()
.Concat(context.Type.GetProperties(bindingFlags))
.ToArray();
var hasNullableEnumMembers = members.Any(x => TypeHelper.IsNullableEnum(x.GetMemberType()));
if (!hasNullableEnumMembers) { return; }
schema.Properties.Where(x => !x.Value.Nullable).ForEach(property =>
{
var name = property.Key;
var possibleNames = new string[]
{
name,
TextCaseHelper.ToPascalCase(name),
}; // handle different cases
var sourceMember = possibleNames
.Select(n => context.Type.GetMember(n, bindingFlags).FirstOrDefault())
.Where(x => x != null)
.FirstOrDefault();
if (sourceMember == null) { return; }
var sourceMemberType = sourceMember.GetMemberType();
if (sourceMemberType == null || !TypeHelper.IsNullableEnum(sourceMemberType)) { return; }
// manual nullability fixes
if (property.Value.Reference != null)
{
// option 1 - OpenAPI 3.1
// https://stackoverflow.com/a/48114924/5168794
//property.Value.AnyOf = new List<OpenApiSchema>()
//{
// new OpenApiSchema
// {
// Type = "null",
// },
// new OpenApiSchema
// {
// Reference = property.Value.Reference,
// },
//};
// property.Value.Reference = null;
// option 2 - OpenAPI 3.0
// https://stackoverflow.com/a/48114924/5168794
property.Value.Nullable = true;
property.Value.AllOf = new List<OpenApiSchema>()
{
new OpenApiSchema
{
Reference = property.Value.Reference,
},
};
property.Value.Reference = null;
// option 3 - OpenAPI 3.0
// https://stackoverflow.com/a/23737104/5168794
//property.Value.OneOf = new List<OpenApiSchema>()
//{
// new OpenApiSchema
// {
// Type = "null",
// },
// new OpenApiSchema
// {
// Reference = property.Value.Reference,
// },
//};
//property.Value.Reference = null;
}
});
}
}
public static class TypeHelper
{
/// <summary>
/// Checks if type is CLR type.
/// </summary>
public static bool IsCLR(Type type) => type.Assembly == typeof(int).Assembly;
/// <summary>
/// Checks if type is Microsoft type.
/// </summary>
public static bool IsMicrosoft(Type type) => type.Assembly.FullName?.StartsWith("Microsoft") ?? false;
/// <summary>
/// Checks if type is value type.
/// </summary>
public static bool IsValue(Type type) => type.IsValueType;
/// <summary>
/// Checks if type is reference type.
/// </summary>
public static bool IsReference(Type type) => !type.IsValueType && type.IsClass;
/// <summary>
/// Checks if property type is nullable reference type.
/// NB: Reflection APIs for nullability information are available from .NET 6 Preview 7.
/// </summary>
public static bool IsNullableReferenceProperty(PropertyInfo property) =>
new NullabilityInfoContext().Create(property).WriteState is NullabilityState.Nullable;
/// <summary>
/// Checks if type is enum type.
/// </summary>
public static bool IsEnum(Type type) => type.IsEnum || (Nullable.GetUnderlyingType(type)?.IsEnum ?? false);
/// <summary>
/// Checks if type is nullable enum type.
/// </summary>
public static bool IsNullableEnum(Type type) => Nullable.GetUnderlyingType(type)?.IsEnum ?? false;
/// <summary>
/// Checks if type is not nullable enum type.
/// </summary>
public static bool IsNotNullableEnum(Type type) => IsEnum(type) && !IsNullableEnum(type);
}
Based on:
Upvotes: 0