Skip to content

Commit 10980e6

Browse files
committed
Centralize some schema type arrays, extension to unwrap constructed generic type
1 parent 1ec31d2 commit 10980e6

9 files changed

+108
-109
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
2+
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
3+
4+
#pragma warning disable AV1008 // Class should not be static
5+
6+
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects;
7+
8+
internal static class JsonApiSchemaFacts
9+
{
10+
private static readonly Type[] RequestSchemaTypes =
11+
[
12+
typeof(ResourcePostRequestDocument<>),
13+
typeof(ResourcePatchRequestDocument<>),
14+
typeof(ToOneRelationshipInRequest<>),
15+
typeof(NullableToOneRelationshipInRequest<>),
16+
typeof(ToManyRelationshipInRequest<>)
17+
];
18+
19+
private static readonly Type[] ResponseSchemaTypes =
20+
[
21+
typeof(ResourceCollectionResponseDocument<>),
22+
typeof(PrimaryResourceResponseDocument<>),
23+
typeof(SecondaryResourceResponseDocument<>),
24+
typeof(NullableSecondaryResourceResponseDocument<>),
25+
typeof(ResourceIdentifierCollectionResponseDocument<>),
26+
typeof(ResourceIdentifierResponseDocument<>),
27+
typeof(NullableResourceIdentifierResponseDocument<>),
28+
typeof(ErrorResponseDocument)
29+
];
30+
31+
private static readonly Type[] SchemaTypesHavingNullableDataProperty =
32+
[
33+
typeof(NullableSecondaryResourceResponseDocument<>),
34+
typeof(NullableResourceIdentifierResponseDocument<>),
35+
typeof(NullableToOneRelationshipInRequest<>),
36+
typeof(NullableToOneRelationshipInResponse<>)
37+
];
38+
39+
private static readonly Type[] RelationshipInResponseSchemaTypes =
40+
[
41+
typeof(ToOneRelationshipInResponse<>),
42+
typeof(ToManyRelationshipInResponse<>),
43+
typeof(NullableToOneRelationshipInResponse<>)
44+
];
45+
46+
public static bool RequiresCustomSchemaGenerator(Type schemaType)
47+
{
48+
// Subset of the entry types Swashbuckle calls us for, which must be handled by our custom schema generators.
49+
50+
Type lookupType = schemaType.ConstructedToOpenType();
51+
return RequestSchemaTypes.Contains(lookupType) || ResponseSchemaTypes.Contains(lookupType);
52+
}
53+
54+
public static bool IsRequestSchemaType(Type schemaType)
55+
{
56+
Type lookupType = schemaType.ConstructedToOpenType();
57+
return RequestSchemaTypes.Contains(lookupType);
58+
}
59+
60+
public static bool HasNullableDataProperty(Type schemaType)
61+
{
62+
// Swashbuckle infers non-nullable because our Data properties are [Required].
63+
64+
Type lookupType = schemaType.ConstructedToOpenType();
65+
return SchemaTypesHavingNullableDataProperty.Contains(lookupType);
66+
}
67+
68+
public static bool IsRelationshipInResponseType(Type schemaType)
69+
{
70+
Type lookupType = schemaType.ConstructedToOpenType();
71+
return RelationshipInResponseSchemaTypes.Contains(lookupType);
72+
}
73+
}

src/JsonApiDotNetCore.OpenApi/JsonApiOperationIdSelector.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,7 @@ private static Type GetDocumentType(ApiDescription endpoint)
8686
}
8787

8888
ControllerParameterDescriptor? requestBodyDescriptor = endpoint.ActionDescriptor.GetBodyParameterDescriptor();
89-
90-
Type documentType = requestBodyDescriptor?.ParameterType.GetGenericTypeDefinition() ??
91-
GetGenericTypeDefinition(producesResponseTypeAttribute.Type) ?? producesResponseTypeAttribute.Type;
89+
Type documentType = (requestBodyDescriptor?.ParameterType ?? producesResponseTypeAttribute.Type).ConstructedToOpenType();
9290

9391
if (documentType == typeof(ResourceCollectionResponseDocument<>) && endpoint.ParameterDescriptions.Count > 0)
9492
{
@@ -98,11 +96,6 @@ private static Type GetDocumentType(ApiDescription endpoint)
9896
return documentType;
9997
}
10098

101-
private static Type? GetGenericTypeDefinition(Type type)
102-
{
103-
return type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : null;
104-
}
105-
10699
private string ApplyTemplate(string operationIdTemplate, ResourceType resourceType, ApiDescription endpoint)
107100
{
108101
if (endpoint.RelativePath == null || endpoint.HttpMethod == null)

src/JsonApiDotNetCore.OpenApi/JsonApiRequestFormatMetadataProvider.cs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using JsonApiDotNetCore.Middleware;
2-
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
3-
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
2+
using JsonApiDotNetCore.OpenApi.JsonApiObjects;
43
using Microsoft.AspNetCore.Mvc.ApiExplorer;
54
using Microsoft.AspNetCore.Mvc.Formatters;
65
using Microsoft.Net.Http.Headers;
@@ -9,15 +8,6 @@ namespace JsonApiDotNetCore.OpenApi;
98

109
internal sealed class JsonApiRequestFormatMetadataProvider : IInputFormatter, IApiRequestFormatMetadataProvider
1110
{
12-
private static readonly Type[] JsonApiRequestOpenTypes =
13-
[
14-
typeof(ToManyRelationshipInRequest<>),
15-
typeof(ToOneRelationshipInRequest<>),
16-
typeof(NullableToOneRelationshipInRequest<>),
17-
typeof(ResourcePostRequestDocument<>),
18-
typeof(ResourcePatchRequestDocument<>)
19-
];
20-
2111
/// <inheritdoc />
2212
public bool CanRead(InputFormatterContext context)
2313
{
@@ -36,8 +26,7 @@ public IReadOnlyList<string> GetSupportedContentTypes(string contentType, Type o
3626
ArgumentGuard.NotNullNorEmpty(contentType);
3727
ArgumentGuard.NotNull(objectType);
3828

39-
if (contentType == HeaderConstants.MediaType && objectType.IsConstructedGenericType &&
40-
JsonApiRequestOpenTypes.Contains(objectType.GetGenericTypeDefinition()))
29+
if (contentType == HeaderConstants.MediaType && JsonApiSchemaFacts.IsRequestSchemaType(objectType))
4130
{
4231
return new MediaTypeCollection
4332
{

src/JsonApiDotNetCore.OpenApi/JsonApiSchemaIdSelector.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ public string GetSchemaId(Type type)
6464
return resourceType.PublicName.Singularize();
6565
}
6666

67-
if (type.IsConstructedGenericType)
68-
{
69-
Type openType = type.GetGenericTypeDefinition();
67+
Type openType = type.ConstructedToOpenType();
7068

69+
if (openType != type)
70+
{
7171
if (TypeToSchemaTemplateMap.TryGetValue(openType, out string? schemaTemplate))
7272
{
7373
Type resourceClrType = type.GetGenericArguments().First();

src/JsonApiDotNetCore.OpenApi/SwaggerComponents/DocumentSchemaGenerator.cs

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System.Reflection;
22
using JsonApiDotNetCore.Configuration;
3-
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
4-
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
3+
using JsonApiDotNetCore.OpenApi.JsonApiObjects;
54
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
65
using Microsoft.OpenApi.Models;
76
using Swashbuckle.AspNetCore.SwaggerGen;
@@ -11,13 +10,6 @@ namespace JsonApiDotNetCore.OpenApi.SwaggerComponents;
1110

1211
internal sealed class DocumentSchemaGenerator
1312
{
14-
private static readonly Type[] JsonApiDocumentWithNullableDataOpenTypes =
15-
[
16-
typeof(NullableSecondaryResourceResponseDocument<>),
17-
typeof(NullableResourceIdentifierResponseDocument<>),
18-
typeof(NullableToOneRelationshipInRequest<>)
19-
];
20-
2113
private readonly SchemaGenerator _defaultSchemaGenerator;
2214
private readonly AbstractResourceDataSchemaGenerator _abstractResourceDataSchemaGenerator;
2315
private readonly ResourceDataSchemaGenerator _resourceDataSchemaGenerator;
@@ -86,7 +78,7 @@ private OpenApiSchema GenerateJsonApiDocumentSchema(Type documentType, SchemaRep
8678
OpenApiSchema referenceSchemaForDocument = _defaultSchemaGenerator.GenerateSchema(documentType, schemaRepository);
8779
OpenApiSchema fullSchemaForDocument = schemaRepository.Schemas[referenceSchemaForDocument.Reference.Id];
8880

89-
if (IsDataPropertyNullableInDocument(documentType))
81+
if (JsonApiSchemaFacts.HasNullableDataProperty(documentType))
9082
{
9183
SetDataSchemaToNullable(fullSchemaForDocument);
9284
}
@@ -103,7 +95,7 @@ private static Type GetInnerTypeOfDataProperty(Type documentType)
10395
throw new UnreachableCodeException();
10496
}
10597

106-
return dataProperty.PropertyType.GetGenericTypeDefinition().IsAssignableTo(typeof(ICollection<>))
98+
return dataProperty.PropertyType.ConstructedToOpenType().IsAssignableTo(typeof(ICollection<>))
10799
? dataProperty.PropertyType.GenericTypeArguments[0]
108100
: dataProperty.PropertyType;
109101
}
@@ -129,13 +121,6 @@ private void EnsureResourceTypesAreMappedInDiscriminator(Type resourceDataConstr
129121
}
130122
}
131123

132-
private static bool IsDataPropertyNullableInDocument(Type documentType)
133-
{
134-
Type documentOpenType = documentType.GetGenericTypeDefinition();
135-
136-
return JsonApiDocumentWithNullableDataOpenTypes.Contains(documentOpenType);
137-
}
138-
139124
private static void SetDataSchemaToNullable(OpenApiSchema fullSchemaForDocument)
140125
{
141126
OpenApiSchema referenceSchemaForData = fullSchemaForDocument.Properties[JsonApiPropertyName.Data];

src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System.Reflection;
22
using JsonApiDotNetCore.Controllers;
3-
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
4-
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
3+
using JsonApiDotNetCore.OpenApi.JsonApiObjects;
54
using Microsoft.AspNetCore.Mvc.ApiExplorer;
65
using Microsoft.OpenApi.Models;
76
using Swashbuckle.AspNetCore.SwaggerGen;
@@ -11,24 +10,6 @@ namespace JsonApiDotNetCore.OpenApi.SwaggerComponents;
1110

1211
internal sealed class JsonApiSchemaGenerator : ISchemaGenerator
1312
{
14-
// List of all the root types we're being called for, that need to be handled by our custom schema generators.
15-
private static readonly Type[] JsonApiDocumentTypes =
16-
[
17-
typeof(ResourceCollectionResponseDocument<>),
18-
typeof(PrimaryResourceResponseDocument<>),
19-
typeof(SecondaryResourceResponseDocument<>),
20-
typeof(NullableSecondaryResourceResponseDocument<>),
21-
typeof(ResourcePostRequestDocument<>),
22-
typeof(ResourcePatchRequestDocument<>),
23-
typeof(ResourceIdentifierCollectionResponseDocument<>),
24-
typeof(ResourceIdentifierResponseDocument<>),
25-
typeof(NullableResourceIdentifierResponseDocument<>),
26-
typeof(ErrorResponseDocument),
27-
typeof(ToManyRelationshipInRequest<>),
28-
typeof(ToOneRelationshipInRequest<>),
29-
typeof(NullableToOneRelationshipInRequest<>)
30-
];
31-
3213
private static readonly OpenApiSchema IdTypeSchema = new()
3314
{
3415
Type = "string"
@@ -66,7 +47,7 @@ public OpenApiSchema GenerateSchema(Type modelType, SchemaRepository schemaRepos
6647
: jsonApiDocumentSchema;
6748
}
6849

69-
if (IsJsonApiDocument(modelType))
50+
if (JsonApiSchemaFacts.RequiresCustomSchemaGenerator(modelType))
7051
{
7152
_ = _documentSchemaGenerator.GenerateSchema(modelType, schemaRepository);
7253

@@ -80,10 +61,4 @@ private static bool IsJsonApiParameter(ParameterInfo parameter)
8061
{
8162
return parameter.Member.DeclaringType != null && parameter.Member.DeclaringType.IsAssignableTo(typeof(CoreJsonApiController));
8263
}
83-
84-
private static bool IsJsonApiDocument(Type type)
85-
{
86-
Type documentType = type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : type;
87-
return JsonApiDocumentTypes.Contains(documentType);
88-
}
8964
}

src/JsonApiDotNetCore.OpenApi/SwaggerComponents/LinksVisibilitySchemaGenerator.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal sealed class LinksVisibilitySchemaGenerator
2828
private const LinkTypes RelationshipLinkTypes = LinkTypes.Self | LinkTypes.Related;
2929
private const LinkTypes ResourceLinkTypes = LinkTypes.Self;
3030

31-
private static readonly Dictionary<Type, LinkTypes> LinksInJsonApiComponentTypes = new()
31+
private static readonly Dictionary<Type, LinkTypes> LinksInJsonApiSchemaTypes = new()
3232
{
3333
[typeof(NullableSecondaryResourceResponseDocument<>)] = ResourceTopLinkTypes,
3434
[typeof(PrimaryResourceResponseDocument<>)] = ResourceTopLinkTypes,
@@ -73,9 +73,9 @@ public void UpdateSchemaForTopLevel(Type modelType, OpenApiSchema fullSchemaForL
7373
ArgumentGuard.NotNull(modelType);
7474
ArgumentGuard.NotNull(fullSchemaForLinksContainer);
7575

76-
Type lookupType = modelType.IsConstructedGenericType ? modelType.GetGenericTypeDefinition() : modelType;
76+
Type lookupType = modelType.ConstructedToOpenType();
7777

78-
if (LinksInJsonApiComponentTypes.TryGetValue(lookupType, out LinkTypes possibleLinkTypes))
78+
if (LinksInJsonApiSchemaTypes.TryGetValue(lookupType, out LinkTypes possibleLinkTypes))
7979
{
8080
UpdateLinksProperty(fullSchemaForLinksContainer, _lazyLinksVisibility.Value.TopLevelLinks, possibleLinkTypes, schemaRepository);
8181
}
@@ -86,7 +86,7 @@ public void UpdateSchemaForResource(ResourceTypeInfo resourceTypeInfo, OpenApiSc
8686
ArgumentGuard.NotNull(resourceTypeInfo);
8787
ArgumentGuard.NotNull(fullSchemaForResourceData);
8888

89-
if (LinksInJsonApiComponentTypes.TryGetValue(resourceTypeInfo.ResourceDataOpenType, out LinkTypes possibleLinkTypes))
89+
if (LinksInJsonApiSchemaTypes.TryGetValue(resourceTypeInfo.ResourceDataOpenType, out LinkTypes possibleLinkTypes))
9090
{
9191
UpdateLinksProperty(fullSchemaForResourceData, _lazyLinksVisibility.Value.ResourceLinks, possibleLinkTypes, schemaRepository);
9292
}
@@ -97,9 +97,9 @@ public void UpdateSchemaForRelationship(Type modelType, OpenApiSchema fullSchema
9797
ArgumentGuard.NotNull(modelType);
9898
ArgumentGuard.NotNull(fullSchemaForRelationship);
9999

100-
Type lookupType = modelType.GetGenericTypeDefinition();
100+
Type lookupType = modelType.ConstructedToOpenType();
101101

102-
if (LinksInJsonApiComponentTypes.TryGetValue(lookupType, out LinkTypes possibleLinkTypes))
102+
if (LinksInJsonApiSchemaTypes.TryGetValue(lookupType, out LinkTypes possibleLinkTypes))
103103
{
104104
UpdateLinksProperty(fullSchemaForRelationship, _lazyLinksVisibility.Value.RelationshipLinks, possibleLinkTypes, schemaRepository);
105105
}

src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldSchemaBuilder.cs

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Reflection;
22
using JsonApiDotNetCore.OpenApi.JsonApiMetadata;
3-
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
3+
using JsonApiDotNetCore.OpenApi.JsonApiObjects;
44
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
55
using JsonApiDotNetCore.Resources.Annotations;
66
using Microsoft.OpenApi.Models;
@@ -11,19 +11,6 @@ namespace JsonApiDotNetCore.OpenApi.SwaggerComponents;
1111

1212
internal sealed class ResourceFieldSchemaBuilder
1313
{
14-
private static readonly Type[] RelationshipSchemaInResponseOpenTypes =
15-
[
16-
typeof(ToOneRelationshipInResponse<>),
17-
typeof(ToManyRelationshipInResponse<>),
18-
typeof(NullableToOneRelationshipInResponse<>)
19-
];
20-
21-
private static readonly Type[] NullableRelationshipSchemaOpenTypes =
22-
[
23-
typeof(NullableToOneRelationshipInRequest<>),
24-
typeof(NullableToOneRelationshipInResponse<>)
25-
];
26-
2714
private readonly SchemaGenerator _defaultSchemaGenerator;
2815
private readonly ResourceIdentifierSchemaGenerator _resourceIdentifierSchemaGenerator;
2916
private readonly LinksVisibilitySchemaGenerator _linksVisibilitySchemaGenerator;
@@ -141,8 +128,8 @@ private Type GetRepresentedTypeForAttributeSchema(AttrAttribute attribute)
141128

142129
private bool IsFieldRequired(ResourceFieldAttribute field)
143130
{
144-
bool isSchemaForPostResourceRequest = _resourceTypeInfo.ResourceDataOpenType == typeof(ResourceDataInPostRequest<>);
145-
return isSchemaForPostResourceRequest && _resourceFieldValidationMetadataProvider.IsRequired(field);
131+
bool isPostRequestSchemaType = _resourceTypeInfo.ResourceDataOpenType == typeof(ResourceDataInPostRequest<>);
132+
return isPostRequestSchemaType && _resourceFieldValidationMetadataProvider.IsRequired(field);
146133
}
147134

148135
public void SetMembersOfRelationships(OpenApiSchema fullSchemaForRelationships, SchemaRepository schemaRepository)
@@ -189,9 +176,8 @@ private void AddRelationshipSchemaToResourceData(RelationshipAttribute relations
189176

190177
private Type GetRelationshipSchemaType(RelationshipAttribute relationship, Type resourceDataConstructedType)
191178
{
192-
return resourceDataConstructedType.GetGenericTypeDefinition().IsAssignableTo(typeof(ResourceDataInResponse<>))
193-
? _relationshipTypeFactory.GetForResponse(relationship)
194-
: _relationshipTypeFactory.GetForRequest(relationship);
179+
bool isResponseSchemaType = resourceDataConstructedType.ConstructedToOpenType().IsAssignableTo(typeof(ResourceDataInResponse<>));
180+
return isResponseSchemaType ? _relationshipTypeFactory.GetForResponse(relationship) : _relationshipTypeFactory.GetForRequest(relationship);
195181
}
196182

197183
private OpenApiSchema? GetReferenceSchemaForRelationship(Type relationshipSchemaType, SchemaRepository schemaRepository)
@@ -205,12 +191,12 @@ private OpenApiSchema CreateRelationshipReferenceSchema(Type relationshipSchemaT
205191

206192
OpenApiSchema fullSchema = schemaRepository.Schemas[referenceSchema.Reference.Id];
207193

208-
if (IsDataPropertyNullableInRelationshipSchemaType(relationshipSchemaType))
194+
if (JsonApiSchemaFacts.HasNullableDataProperty(relationshipSchemaType))
209195
{
210196
fullSchema.Properties[JsonApiPropertyName.Data].Nullable = true;
211197
}
212198

213-
if (IsRelationshipInResponseType(relationshipSchemaType))
199+
if (JsonApiSchemaFacts.IsRelationshipInResponseType(relationshipSchemaType))
214200
{
215201
_linksVisibilitySchemaGenerator.UpdateSchemaForRelationship(relationshipSchemaType, fullSchema, schemaRepository);
216202

@@ -221,17 +207,4 @@ private OpenApiSchema CreateRelationshipReferenceSchema(Type relationshipSchemaT
221207

222208
return referenceSchema;
223209
}
224-
225-
private static bool IsRelationshipInResponseType(Type relationshipSchemaType)
226-
{
227-
Type relationshipSchemaOpenType = relationshipSchemaType.GetGenericTypeDefinition();
228-
229-
return RelationshipSchemaInResponseOpenTypes.Contains(relationshipSchemaOpenType);
230-
}
231-
232-
private static bool IsDataPropertyNullableInRelationshipSchemaType(Type relationshipSchemaType)
233-
{
234-
Type relationshipSchemaOpenType = relationshipSchemaType.GetGenericTypeDefinition();
235-
return NullableRelationshipSchemaOpenTypes.Contains(relationshipSchemaOpenType);
236-
}
237210
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace JsonApiDotNetCore.OpenApi;
2+
3+
internal static class TypeExtensions
4+
{
5+
public static Type ConstructedToOpenType(this Type type)
6+
{
7+
ArgumentGuard.NotNull(type);
8+
9+
return type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : type;
10+
}
11+
}

0 commit comments

Comments
 (0)