Skip to content

Commit

Permalink
Mark non-nullable properties required when SupportNonNullableReferenc…
Browse files Browse the repository at this point in the history
…eTypes is enabled and property is declared non-nullable. Fixes domaindrivendev#2623 for #3
  • Loading branch information
Havunen committed Feb 22, 2024
1 parent 1bccadb commit 147c6fb
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ public DataProperty(
}

public string Name { get; }
public bool IsRequired { get; }
public bool IsNullable { get; }
public bool IsRequired { get; internal set; }
public bool IsNullable { get; internal set; }
public bool IsReadOnly { get; }
public bool IsWriteOnly { get; }
public Type MemberType { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,22 @@ private OpenApiSchema GenerateSchemaForMember(
{
var requiredAttribute = customAttributes.OfType<RequiredAttribute>().FirstOrDefault();
var hasRequiredMemberAttribute = customAttributes.OfType<RequiredMemberAttribute>().Any();
schema.Nullable = _generatorOptions.SupportNonNullableReferenceTypes
? dataProperty.IsNullable && requiredAttribute == null && !hasRequiredMemberAttribute && !memberInfo.IsNonNullableReferenceType()
: dataProperty.IsNullable && requiredAttribute == null && !hasRequiredMemberAttribute;

if (_generatorOptions.SupportNonNullableReferenceTypes)
{
schema.Nullable = dataProperty.IsNullable && requiredAttribute == null &&
!hasRequiredMemberAttribute && !memberInfo.IsNonNullableReferenceType();
if (!schema.Nullable)
{
dataProperty.IsNullable = false;
dataProperty.IsRequired = true;
}
}
else
{
schema.Nullable = dataProperty.IsNullable && requiredAttribute == null &&
!hasRequiredMemberAttribute;
}

schema.ReadOnly = dataProperty.IsReadOnly;
schema.WriteOnly = dataProperty.IsWriteOnly;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<IsUnitTestProject>true</IsUnitTestProject>
<IsTestProject>true</IsTestProject>
<DefineTrace>true</DefineTrace>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
namespace DotSwashbuckle.AspNetCore.SwaggerGen.Test.Fixtures
{
public sealed class TestClass
{
public TestClass(string requiredString, string optionalString)
{
RequiredString = requiredString;
OptionalString = optionalString;
}

public string RequiredString { get; }
public string? OptionalString { get; }
}

public sealed record TestRecordPrimary(string RequiredString, string? OptionalString);
public sealed record TestRecordPrimaryInverse(string? OptionalString, string RequiredString);

public sealed record TestValueRecordPrimary(int RequiredString, int? OptionalString);
public sealed record TestValueRecordPrimaryInverse(int? OptionalString, int RequiredString);

public sealed record TestRecord
{
public TestRecord(string requiredString, string? optionalString)
{
RequiredString = requiredString;
OptionalString = optionalString;
}

public string RequiredString { get; }

public string? OptionalString { get; }
}

public sealed record TestRecordInitOptional
{
public TestRecordInitOptional(string? optionalString)
{
OptionalString = optionalString;
}

public string RequiredString { get; set; } = string.Empty;

public string? OptionalString { get; }
}

public sealed record TestRecordInitRequired
{
public TestRecordInitRequired(string requiredString)
{
RequiredString = requiredString;
}

public string RequiredString { get; }

public string? OptionalString { get; set; }
}

public sealed record TestRecordMixed(string RequiredString)
{
public string? OptionalString { get; }
}

public sealed record TestRecordMixedSwapped(string? OptionalString)
{
public string RequiredString { get; set; } = string.Empty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Net;
using DotSwashbuckle.AspNetCore.SwaggerGen.Test.Fixtures;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
Expand Down Expand Up @@ -81,6 +82,47 @@ public void GenerateSchema_GeneratesPrimitiveSchema_IfPrimitiveOrNullablePrimiti
Assert.Equal(expectedFormat, schema.Format);
}

[Theory]
[InlineData(typeof(TestRecordMixedSwapped), nameof(TestRecordMixedSwapped.RequiredString), false, true)]
[InlineData(typeof(TestRecordMixedSwapped), nameof(TestRecordMixedSwapped.OptionalString), true, false)]
[InlineData(typeof(TestClass), nameof(TestClass.RequiredString), false, true)]
[InlineData(typeof(TestClass), nameof(TestClass.OptionalString), true, false)]
[InlineData(typeof(TestRecord), nameof(TestRecord.RequiredString), false, true)]
[InlineData(typeof(TestRecord), nameof(TestRecord.OptionalString), true, false)]
[InlineData(typeof(TestRecordMixed), nameof(TestRecordMixed.RequiredString), false, true)]
[InlineData(typeof(TestRecordMixed), nameof(TestRecordMixed.OptionalString), true, false)]
[InlineData(typeof(TestRecordInitOptional), nameof(TestRecordInitOptional.RequiredString), false, true)]
[InlineData(typeof(TestRecordInitOptional), nameof(TestRecordInitOptional.OptionalString), true, false)]
[InlineData(typeof(TestRecordInitRequired), nameof(TestRecordInitRequired.RequiredString), false, true)]
[InlineData(typeof(TestRecordInitRequired), nameof(TestRecordInitRequired.OptionalString), true, false)]
[InlineData(typeof(TestRecordPrimary), nameof(TestRecordPrimary.RequiredString), false, true)]
[InlineData(typeof(TestRecordPrimary), nameof(TestRecordPrimary.OptionalString), true, false)]
[InlineData(typeof(TestRecordPrimaryInverse), nameof(TestRecordPrimaryInverse.RequiredString), false, true)]
[InlineData(typeof(TestRecordPrimaryInverse), nameof(TestRecordPrimaryInverse.OptionalString), true, false)]
[InlineData(typeof(TestValueRecordPrimary), nameof(TestValueRecordPrimary.RequiredString), false, true)]
[InlineData(typeof(TestValueRecordPrimary), nameof(TestValueRecordPrimary.OptionalString), true, false)]
[InlineData(typeof(TestValueRecordPrimaryInverse), nameof(TestValueRecordPrimaryInverse.RequiredString), false, true)]
[InlineData(typeof(TestValueRecordPrimaryInverse), nameof(TestValueRecordPrimaryInverse.OptionalString), true, false)]
public void TestNullable_And_Required_When_SupportNonNullableReferenceTypes_Enabled(
Type type,
string propertyName,
bool expectedNullable,
bool expectedRequired
)
{
var schemaRepository = new SchemaRepository();

var referenceSchema = Subject(
configureGenerator: c => c.SupportNonNullableReferenceTypes = true
).GenerateSchema(type, schemaRepository);

Assert.NotNull(referenceSchema.Reference);
var schema = schemaRepository.Schemas[referenceSchema.Reference.Id];
schema.Properties.TryGetValue(propertyName, out var propertySchema);
Assert.Equal(expectedNullable, propertySchema.Nullable);
Assert.Equal(expectedRequired, schema.Required.Contains(propertyName));
}

[Theory]
[InlineData(typeof(IntEnum), "integer", "int32", "2", "4", "8")]
[InlineData(typeof(LongEnum), "integer", "int64", "2", "4", "8")]
Expand Down

0 comments on commit 147c6fb

Please sign in to comment.