Skip to content

Commit 43b1b34

Browse files
Merge pull request #2166 from Eneuman/bug/nullabillity-in-generics
Fixed nullabillity problems with dictionaries
2 parents 07d33a7 + a8bb26f commit 43b1b34

File tree

4 files changed

+63
-4
lines changed

4 files changed

+63
-4
lines changed

src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/MemberInfoExtensions.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,31 @@ public static bool IsNonNullableReferenceType(this MemberInfo memberInfo)
5858
return false;
5959
}
6060

61+
public static bool IsDictionaryValueNonNullable(this MemberInfo memberInfo)
62+
{
63+
var memberType = memberInfo.MemberType == MemberTypes.Field
64+
? ((FieldInfo)memberInfo).FieldType
65+
: ((PropertyInfo)memberInfo).PropertyType;
66+
67+
if (memberType.IsValueType) return false;
68+
69+
var nullableAttribute = memberInfo.GetNullableAttribute();
70+
71+
if (nullableAttribute == null)
72+
{
73+
return memberInfo.GetNullableFallbackValue();
74+
}
75+
76+
if (nullableAttribute.GetType().GetField(NullableFlagsFieldName) is FieldInfo field &&
77+
field.GetValue(nullableAttribute) is byte[] flags &&
78+
flags.Length == 3 && flags[2] == 1)
79+
{
80+
return true;
81+
}
82+
83+
return false;
84+
}
85+
6186
private static object GetNullableAttribute(this MemberInfo memberInfo)
6287
{
6388
var nullableAttribute = memberInfo.GetCustomAttributes()

src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ private OpenApiSchema GenerateSchemaForMember(
8787
schema.Deprecated = true;
8888
}
8989

90+
// NullableAttribute behaves diffrently for Dictionaries
91+
if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
92+
{
93+
schema.AdditionalProperties.Nullable = !memberInfo.IsDictionaryValueNonNullable();
94+
}
95+
9096
schema.ApplyValidationAttributes(customAttributes);
9197

9298
ApplyFilters(schema, modelType, schemaRepository, memberInfo: memberInfo);

test/Swashbuckle.AspNetCore.SwaggerGen.Test/SchemaGenerator/JsonSerializerSchemaGeneratorTests.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ public void GenerateSchema_SupportsOption_UseAllOfForPolymorphism()
512512
Assert.NotNull(schema.OneOf[0].Reference);
513513
var baseSchema = schemaRepository.Schemas[schema.OneOf[0].Reference.Id];
514514
Assert.Equal("object", baseSchema.Type);
515-
Assert.Equal(new[] { "BaseProperty"}, baseSchema.Properties.Keys);
515+
Assert.Equal(new[] { "BaseProperty" }, baseSchema.Properties.Keys);
516516
// The first sub type schema
517517
Assert.NotNull(schema.OneOf[1].Reference);
518518
var subType1Schema = schemaRepository.Schemas[schema.OneOf[1].Reference.Id];
@@ -586,10 +586,34 @@ public void GenerateSchema_SupportsOption_SupportNonNullableReferenceTypes(
586586
Assert.Equal(expectedNullable, propertySchema.Nullable);
587587
}
588588

589+
[Theory]
590+
[InlineData(typeof(TypeWithNullableContext), nameof(TypeWithNullableContext.NullableDictionaryWithNonNullableContent), true, false)]
591+
[InlineData(typeof(TypeWithNullableContext), nameof(TypeWithNullableContext.NonNullableDictionaryWithNonNullableContent), false, false)]
592+
[InlineData(typeof(TypeWithNullableContext), nameof(TypeWithNullableContext.NonNullableDictionaryWithNullableContent), false, true)]
593+
[InlineData(typeof(TypeWithNullableContext), nameof(TypeWithNullableContext.NullableDictionaryWithNullableContent), true, true)]
594+
public void GenerateSchema_SupportsOption_SupportNonNullableReferenceTypes_NullableAttribute_Compiler_Optimizations_Situations(
595+
Type declaringType,
596+
string propertyName,
597+
bool expectedNullableProperty,
598+
bool expectedNullableContent)
599+
{
600+
var subject = Subject(
601+
configureGenerator: c => c.SupportNonNullableReferenceTypes = true
602+
);
603+
var schemaRepository = new SchemaRepository();
604+
605+
var referenceSchema = subject.GenerateSchema(declaringType, schemaRepository);
606+
607+
var propertySchema = schemaRepository.Schemas[referenceSchema.Reference.Id].Properties[propertyName];
608+
var contentSchema = schemaRepository.Schemas[referenceSchema.Reference.Id].Properties[propertyName].AdditionalProperties;
609+
Assert.Equal(expectedNullableProperty, propertySchema.Nullable);
610+
Assert.Equal(expectedNullableContent, contentSchema.Nullable);
611+
}
612+
589613
[Theory]
590614
[InlineData(typeof(TypeWithNullableContext), nameof(TypeWithNullableContext.SubTypeWithOneNullableContent), nameof(TypeWithNullableContext.NullableString), true)]
591615
[InlineData(typeof(TypeWithNullableContext), nameof(TypeWithNullableContext.SubTypeWithOneNonNullableContent), nameof(TypeWithNullableContext.NonNullableString), false)]
592-
public void GenerateSchema_SupportsOption_SupportNonNullableReferenceTypes_NullableAttribute_Compiler_Optimizations_Situations(
616+
public void GenerateSchema_SupportsOption_SupportNonNullableReferenceTypesInDictionary_NullableAttribute_Compiler_Optimizations_Situations(
593617
Type declaringType,
594618
string subType,
595619
string propertyName,
@@ -606,7 +630,6 @@ public void GenerateSchema_SupportsOption_SupportNonNullableReferenceTypes_Nulla
606630
Assert.Equal(expectedNullable, propertySchema.Nullable);
607631
}
608632

609-
610633
[Fact]
611634
public void GenerateSchema_HandlesTypesWithNestedTypes()
612635
{
@@ -740,7 +763,7 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonPropertyName()
740763
var referenceSchema = Subject().GenerateSchema(typeof(JsonPropertyNameAnnotatedType), schemaRepository);
741764

742765
var schema = schemaRepository.Schemas[referenceSchema.Reference.Id];
743-
Assert.Equal( new[] { "string-with-json-property-name" }, schema.Properties.Keys.ToArray());
766+
Assert.Equal(new[] { "string-with-json-property-name" }, schema.Properties.Keys.ToArray());
744767
}
745768

746769
[Fact]

test/Swashbuckle.AspNetCore.TestSupport/Fixtures/TypeWithNullableContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ public class TypeWithNullableContext
1515
public List<SubTypeWithOneNullableContent>? NullableList { get; set; }
1616
public List<SubTypeWithOneNonNullableContent> NonNullableList { get; set; } = default!;
1717

18+
public Dictionary<string, string>? NullableDictionaryWithNonNullableContent { get; set; }
19+
public Dictionary<string, string> NonNullableDictionaryWithNonNullableContent { get; set; } = default!;
20+
public Dictionary<string, string?> NonNullableDictionaryWithNullableContent { get; set; } = default!;
21+
public Dictionary<string, string?>? NullableDictionaryWithNullableContent { get; set; }
22+
1823
public class SubTypeWithOneNullableContent
1924
{
2025
public string? NullableString { get; set; }

0 commit comments

Comments
 (0)