Skip to content

Commit c55a2cb

Browse files
committed
Use NullabilityInfoContext for nullability information
1 parent 449a079 commit c55a2cb

File tree

9 files changed

+65
-134
lines changed

9 files changed

+65
-134
lines changed

src/JsonApiDotNetCore.OpenApi/MemberInfoExtensions.cs

-36
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,40 @@
11
using System.ComponentModel.DataAnnotations;
2+
using System.Reflection;
23
using JsonApiDotNetCore.Resources.Annotations;
34
using Swashbuckle.AspNetCore.SwaggerGen;
45

56
namespace JsonApiDotNetCore.OpenApi;
67

78
internal static class ResourceFieldAttributeExtensions
89
{
10+
private static readonly NullabilityInfoContext NullabilityInfoContext = new();
11+
912
public static bool IsNullable(this ResourceFieldAttribute source)
1013
{
11-
TypeCategory fieldTypeCategory = source.Property.GetTypeCategory();
1214
bool hasRequiredAttribute = source.Property.HasAttribute<RequiredAttribute>();
1315

14-
return fieldTypeCategory switch
16+
if (hasRequiredAttribute)
1517
{
16-
TypeCategory.NonNullableReferenceType or TypeCategory.ValueType => false,
17-
TypeCategory.NullableReferenceType or TypeCategory.NullableValueType => !hasRequiredAttribute,
18-
_ => throw new UnreachableCodeException()
19-
};
18+
// Reflects the following cases, independent of NRT setting
19+
// `[Required] int? Number` => not nullable
20+
// `[Required] int Number` => not nullable
21+
// `[Required] string Text` => not nullable
22+
// `[Required] string? Text` => not nullable
23+
// `[Required] string Text` => not nullable
24+
return false;
25+
}
26+
27+
NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(source.Property);
28+
29+
// Reflects the following cases:
30+
// Independent of NRT:
31+
// `int? Number` => nullable
32+
// `int Number` => not nullable
33+
// If NRT is enabled:
34+
// `string? Text` => nullable
35+
// `string Text` => not nullable
36+
// If NRT is disabled:
37+
// `string Text` => nullable
38+
return nullabilityInfo.ReadState is not NullabilityState.NotNull;
2039
}
2140
}

src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs

+11-11
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ internal sealed class JsonApiSchemaGenerator : ISchemaGenerator
2626
typeof(NullableToOneRelationshipInRequest<>)
2727
};
2828

29+
private static readonly Type[] JsonApiDocumentWithNullableDataOpenTypes =
30+
{
31+
typeof(NullableSecondaryResourceResponseDocument<>),
32+
typeof(NullableResourceIdentifierResponseDocument<>),
33+
typeof(NullableToOneRelationshipInRequest<>)
34+
};
35+
2936
private readonly ISchemaGenerator _defaultSchemaGenerator;
3037
private readonly ResourceObjectSchemaGenerator _resourceObjectSchemaGenerator;
3138
private readonly NullableReferenceSchemaGenerator _nullableReferenceSchemaGenerator;
@@ -58,7 +65,7 @@ public OpenApiSchema GenerateSchema(Type type, SchemaRepository schemaRepository
5865
{
5966
OpenApiSchema schema = GenerateJsonApiDocumentSchema(type);
6067

61-
if (IsDataPropertyNullable(type))
68+
if (IsDataPropertyNullableInDocument(type))
6269
{
6370
SetDataObjectSchemaToNullable(schema);
6471
}
@@ -98,18 +105,11 @@ private static bool IsManyDataDocument(Type documentType)
98105
return documentType.BaseType!.GetGenericTypeDefinition() == typeof(ManyData<>);
99106
}
100107

101-
private static bool IsDataPropertyNullable(Type type)
108+
private static bool IsDataPropertyNullableInDocument(Type documentType)
102109
{
103-
PropertyInfo? dataProperty = type.GetProperty(nameof(JsonApiObjectPropertyName.Data));
104-
105-
if (dataProperty == null)
106-
{
107-
throw new UnreachableCodeException();
108-
}
109-
110-
TypeCategory typeCategory = dataProperty.GetTypeCategory();
110+
Type documentOpenType = documentType.GetGenericTypeDefinition();
111111

112-
return typeCategory == TypeCategory.NullableReferenceType;
112+
return JsonApiDocumentWithNullableDataOpenTypes.Contains(documentOpenType);
113113
}
114114

115115
private void SetDataObjectSchemaToNullable(OpenApiSchema referenceSchemaForDocument)

src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs

+21-18
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,21 @@ namespace JsonApiDotNetCore.OpenApi.SwaggerComponents;
1212

1313
internal sealed class ResourceFieldObjectSchemaBuilder
1414
{
15-
private static readonly Type[] RelationshipInResponseOpenTypes =
15+
private static readonly NullabilityInfoContext NullabilityInfoContext = new();
16+
17+
private static readonly Type[] RelationshipSchemaInResponseOpenTypes =
1618
{
1719
typeof(ToOneRelationshipInResponse<>),
1820
typeof(ToManyRelationshipInResponse<>),
1921
typeof(NullableToOneRelationshipInResponse<>)
2022
};
2123

24+
private static readonly Type[] NullableRelationshipSchemaOpenTypes =
25+
{
26+
typeof(NullableToOneRelationshipInRequest<>),
27+
typeof(NullableToOneRelationshipInResponse<>)
28+
};
29+
2230
private readonly ResourceTypeInfo _resourceTypeInfo;
2331
private readonly ISchemaRepositoryAccessor _schemaRepositoryAccessor;
2432
private readonly SchemaGenerator _defaultSchemaGenerator;
@@ -111,15 +119,14 @@ private bool IsFieldRequired(ResourceFieldAttribute field)
111119
return false;
112120
}
113121

114-
TypeCategory fieldTypeCategory = field.Property.GetTypeCategory();
115122
bool hasRequiredAttribute = field.Property.HasAttribute<RequiredAttribute>();
116123

117-
return fieldTypeCategory switch
124+
NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(field.Property);
125+
126+
return field.Property.PropertyType.IsValueType switch
118127
{
119-
TypeCategory.NonNullableReferenceType => hasRequiredAttribute || _options.ValidateModelState,
120-
TypeCategory.ValueType => hasRequiredAttribute,
121-
TypeCategory.NullableReferenceType or TypeCategory.NullableValueType => hasRequiredAttribute,
122-
_ => throw new UnreachableCodeException()
128+
true => hasRequiredAttribute,
129+
false => _options.ValidateModelState ? nullabilityInfo.ReadState == NullabilityState.NotNull || hasRequiredAttribute : hasRequiredAttribute
123130
};
124131
}
125132

@@ -197,7 +204,9 @@ private OpenApiSchema CreateRelationshipSchema(Type relationshipSchemaType)
197204

198205
OpenApiSchema fullSchema = _schemaRepositoryAccessor.Current.Schemas[referenceSchema.Reference.Id];
199206

200-
if (IsDataPropertyNullable(relationshipSchemaType))
207+
Console.WriteLine(relationshipSchemaType.FullName);
208+
209+
if (IsDataPropertyNullableInRelationshipSchemaType(relationshipSchemaType))
201210
{
202211
fullSchema.Properties[JsonApiObjectPropertyName.Data] =
203212
_nullableReferenceSchemaGenerator.GenerateSchema(fullSchema.Properties[JsonApiObjectPropertyName.Data]);
@@ -215,18 +224,12 @@ private static bool IsRelationshipInResponseType(Type relationshipSchemaType)
215224
{
216225
Type relationshipSchemaOpenType = relationshipSchemaType.GetGenericTypeDefinition();
217226

218-
return RelationshipInResponseOpenTypes.Contains(relationshipSchemaOpenType);
227+
return RelationshipSchemaInResponseOpenTypes.Contains(relationshipSchemaOpenType);
219228
}
220229

221-
private static bool IsDataPropertyNullable(Type type)
230+
private static bool IsDataPropertyNullableInRelationshipSchemaType(Type relationshipSchemaType)
222231
{
223-
PropertyInfo? dataProperty = type.GetProperty(nameof(JsonApiObjectPropertyName.Data));
224-
225-
if (dataProperty == null)
226-
{
227-
throw new UnreachableCodeException();
228-
}
229-
230-
return dataProperty.GetTypeCategory() == TypeCategory.NullableReferenceType;
232+
Type relationshipSchemaOpenType = relationshipSchemaType.GetGenericTypeDefinition();
233+
return NullableRelationshipSchemaOpenTypes.Contains(relationshipSchemaOpenType);
231234
}
232235
}

src/JsonApiDotNetCore.OpenApi/TypeCategory.cs

-9
This file was deleted.

test/OpenApiClientTests/MemberInfoExtensions.cs

-37
This file was deleted.

test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs

+8-7
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,25 @@ namespace OpenApiClientTests;
66

77
internal static class PropertyInfoAssertionsExtensions
88
{
9+
private static readonly NullabilityInfoContext NullabilityInfoContext = new();
10+
911
[CustomAssertion]
1012
public static void BeNullable(this PropertyInfoAssertions source, string because = "", params object[] becauseArgs)
1113
{
12-
MemberInfo memberInfo = source.Subject;
14+
PropertyInfo propertyInfo = source.Subject;
1315

14-
TypeCategory typeCategory = memberInfo.GetTypeCategory();
16+
NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(propertyInfo);
1517

16-
typeCategory.Should().Match(category => category == TypeCategory.NullableReferenceType || category == TypeCategory.NullableValueType, because,
17-
becauseArgs);
18+
nullabilityInfo.ReadState.Should().NotBe(NullabilityState.NotNull, because, becauseArgs);
1819
}
1920

2021
[CustomAssertion]
2122
public static void BeNonNullable(this PropertyInfoAssertions source, string because = "", params object[] becauseArgs)
2223
{
23-
MemberInfo memberInfo = source.Subject;
24+
PropertyInfo propertyInfo = source.Subject;
2425

25-
TypeCategory typeCategory = memberInfo.GetTypeCategory();
26+
NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(propertyInfo);
2627

27-
typeCategory.Should().Match(category => category == TypeCategory.NonNullableReferenceType || category == TypeCategory.ValueType, because, becauseArgs);
28+
nullabilityInfo.ReadState.Should().Be(NullabilityState.NotNull, because, becauseArgs);
2829
}
2930
}

test/OpenApiClientTests/SchemaProperties/NullableReferenceTypesDisabled/RequiredAttributesTests.cs

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode;
88
using TestBuildingBlocks;
99
using Xunit;
10-
using NullableReferenceTypesDisabledClient = OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled.GeneratedCode.NullableReferenceTypesDisabledClient;
1110

1211
namespace OpenApiClientTests.SchemaProperties.NullableReferenceTypesDisabled;
1312

test/OpenApiClientTests/TypeCategory.cs

-9
This file was deleted.

0 commit comments

Comments
 (0)