Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,31 @@ public static bool IsNonNullableReferenceType(this MemberInfo memberInfo)
return false;
}

public static bool IsDictionaryValueNonNullable(this MemberInfo memberInfo)
{
var memberType = memberInfo.MemberType == MemberTypes.Field
? ((FieldInfo)memberInfo).FieldType
: ((PropertyInfo)memberInfo).PropertyType;

if (memberType.IsValueType) return false;

var nullableAttribute = memberInfo.GetNullableAttribute();

if (nullableAttribute == null)
{
return memberInfo.GetNullableFallbackValue();
}

if (nullableAttribute.GetType().GetField(NullableFlagsFieldName) is FieldInfo field &&
field.GetValue(nullableAttribute) is byte[] flags &&
flags.Length == 3 && flags[2] == 1)
{
return true;
}

return false;
}

private static object GetNullableAttribute(this MemberInfo memberInfo)
{
var nullableAttribute = memberInfo.GetCustomAttributes()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ private OpenApiSchema GenerateSchemaForMember(
schema.Deprecated = true;
}

// NullableAttribute behaves diffrently for Dictionaries
if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
schema.AdditionalProperties.Nullable = !memberInfo.IsDictionaryValueNonNullable();
}

schema.ApplyValidationAttributes(customAttributes);

ApplyFilters(schema, modelType, schemaRepository, memberInfo: memberInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ public void GenerateSchema_SupportsOption_UseAllOfForPolymorphism()
Assert.NotNull(schema.OneOf[0].Reference);
var baseSchema = schemaRepository.Schemas[schema.OneOf[0].Reference.Id];
Assert.Equal("object", baseSchema.Type);
Assert.Equal(new[] { "BaseProperty"}, baseSchema.Properties.Keys);
Assert.Equal(new[] { "BaseProperty" }, baseSchema.Properties.Keys);
// The first sub type schema
Assert.NotNull(schema.OneOf[1].Reference);
var subType1Schema = schemaRepository.Schemas[schema.OneOf[1].Reference.Id];
Expand Down Expand Up @@ -565,10 +565,34 @@ public void GenerateSchema_SupportsOption_SupportNonNullableReferenceTypes(
Assert.Equal(expectedNullable, propertySchema.Nullable);
}

[Theory]
[InlineData(typeof(TypeWithNullableContext), nameof(TypeWithNullableContext.NullableDictionaryWithNonNullableContent), true, false)]
[InlineData(typeof(TypeWithNullableContext), nameof(TypeWithNullableContext.NonNullableDictionaryWithNonNullableContent), false, false)]
[InlineData(typeof(TypeWithNullableContext), nameof(TypeWithNullableContext.NonNullableDictionaryWithNullableContent), false, true)]
[InlineData(typeof(TypeWithNullableContext), nameof(TypeWithNullableContext.NullableDictionaryWithNullableContent), true, true)]
public void GenerateSchema_SupportsOption_SupportNonNullableReferenceTypes_NullableAttribute_Compiler_Optimizations_Situations(
Type declaringType,
string propertyName,
bool expectedNullableProperty,
bool expectedNullableContent)
{
var subject = Subject(
configureGenerator: c => c.SupportNonNullableReferenceTypes = true
);
var schemaRepository = new SchemaRepository();

var referenceSchema = subject.GenerateSchema(declaringType, schemaRepository);

var propertySchema = schemaRepository.Schemas[referenceSchema.Reference.Id].Properties[propertyName];
var contentSchema = schemaRepository.Schemas[referenceSchema.Reference.Id].Properties[propertyName].AdditionalProperties;
Assert.Equal(expectedNullableProperty, propertySchema.Nullable);
Assert.Equal(expectedNullableContent, contentSchema.Nullable);
}

[Theory]
[InlineData(typeof(TypeWithNullableContext), nameof(TypeWithNullableContext.SubTypeWithOneNullableContent), nameof(TypeWithNullableContext.NullableString), true)]
[InlineData(typeof(TypeWithNullableContext), nameof(TypeWithNullableContext.SubTypeWithOneNonNullableContent), nameof(TypeWithNullableContext.NonNullableString), false)]
public void GenerateSchema_SupportsOption_SupportNonNullableReferenceTypes_NullableAttribute_Compiler_Optimizations_Situations(
public void GenerateSchema_SupportsOption_SupportNonNullableReferenceTypesInDictionary_NullableAttribute_Compiler_Optimizations_Situations(
Type declaringType,
string subType,
string propertyName,
Expand All @@ -585,7 +609,6 @@ public void GenerateSchema_SupportsOption_SupportNonNullableReferenceTypes_Nulla
Assert.Equal(expectedNullable, propertySchema.Nullable);
}


[Fact]
public void GenerateSchema_HandlesTypesWithNestedTypes()
{
Expand Down Expand Up @@ -719,7 +742,7 @@ public void GenerateSchema_HonorsSerializerAttribute_JsonPropertyName()
var referenceSchema = Subject().GenerateSchema(typeof(JsonPropertyNameAnnotatedType), schemaRepository);

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

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public class TypeWithNullableContext
public List<SubTypeWithOneNullableContent>? NullableList { get; set; }
public List<SubTypeWithOneNonNullableContent> NonNullableList { get; set; } = default!;

public Dictionary<string, string>? NullableDictionaryWithNonNullableContent { get; set; }
public Dictionary<string, string> NonNullableDictionaryWithNonNullableContent { get; set; } = default!;
public Dictionary<string, string?> NonNullableDictionaryWithNullableContent { get; set; } = default!;
public Dictionary<string, string?>? NullableDictionaryWithNullableContent { get; set; }

public class SubTypeWithOneNullableContent
{
public string? NullableString { get; set; }
Expand Down