Skip to content

Commit 1f38b99

Browse files
authored
Merge pull request #1522 from json-api-dotnet/refactor-data-properties
Refactor Data properties in component schemas
2 parents 1bdee8b + 10980e6 commit 1f38b99

31 files changed

+246
-262
lines changed

src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/NullableResourceIdentifierResponseDocument.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
99

10-
// Types in the current namespace are never touched by ASP.NET ModelState validation, therefore using a non-nullable reference type for a property does not
10+
// Types in the JsonApiObjects namespace are never touched by ASP.NET ModelState validation, therefore using a non-nullable reference type for a property does not
1111
// imply this property is required. Instead, we use [Required] explicitly, because this is how Swashbuckle is instructed to mark properties as required.
1212
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
13-
internal sealed class NullableResourceIdentifierResponseDocument<TResource> : NullableSingleData<ResourceIdentifier<TResource>>
13+
internal sealed class NullableResourceIdentifierResponseDocument<TResource>
1414
where TResource : IIdentifiable
1515
{
1616
[JsonPropertyName("jsonapi")]
@@ -20,6 +20,10 @@ internal sealed class NullableResourceIdentifierResponseDocument<TResource> : Nu
2020
[JsonPropertyName("links")]
2121
public ResourceIdentifierTopLevelLinks Links { get; set; } = null!;
2222

23+
[Required]
24+
[JsonPropertyName("data")]
25+
public ResourceIdentifier<TResource>? Data { get; set; }
26+
2327
[JsonPropertyName("meta")]
2428
public IDictionary<string, object> Meta { get; set; } = null!;
2529
}

src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/NullableSecondaryResourceResponseDocument.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
99

1010
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
11-
internal sealed class NullableSecondaryResourceResponseDocument<TResource> : NullableSingleData<ResourceDataInResponse<TResource>>
11+
internal sealed class NullableSecondaryResourceResponseDocument<TResource>
1212
where TResource : IIdentifiable
1313
{
1414
[JsonPropertyName("jsonapi")]
@@ -18,6 +18,10 @@ internal sealed class NullableSecondaryResourceResponseDocument<TResource> : Nul
1818
[JsonPropertyName("links")]
1919
public ResourceTopLevelLinks Links { get; set; } = null!;
2020

21+
[Required]
22+
[JsonPropertyName("data")]
23+
public ResourceDataInResponse<TResource>? Data { get; set; }
24+
2125
[JsonPropertyName("included")]
2226
public IList<ResourceData> Included { get; set; } = null!;
2327

src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/PrimaryResourceResponseDocument.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
99

1010
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
11-
internal sealed class PrimaryResourceResponseDocument<TResource> : SingleData<ResourceDataInResponse<TResource>>
11+
internal sealed class PrimaryResourceResponseDocument<TResource>
1212
where TResource : IIdentifiable
1313
{
1414
[JsonPropertyName("jsonapi")]
@@ -18,6 +18,10 @@ internal sealed class PrimaryResourceResponseDocument<TResource> : SingleData<Re
1818
[JsonPropertyName("links")]
1919
public ResourceTopLevelLinks Links { get; set; } = null!;
2020

21+
[Required]
22+
[JsonPropertyName("data")]
23+
public ResourceDataInResponse<TResource> Data { get; set; } = null!;
24+
2125
[JsonPropertyName("included")]
2226
public IList<ResourceData> Included { get; set; } = null!;
2327

src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceCollectionResponseDocument.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
99

1010
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
11-
internal sealed class ResourceCollectionResponseDocument<TResource> : ManyData<ResourceDataInResponse<TResource>>
11+
internal sealed class ResourceCollectionResponseDocument<TResource>
1212
where TResource : IIdentifiable
1313
{
1414
[JsonPropertyName("jsonapi")]
@@ -18,6 +18,10 @@ internal sealed class ResourceCollectionResponseDocument<TResource> : ManyData<R
1818
[JsonPropertyName("links")]
1919
public ResourceCollectionTopLevelLinks Links { get; set; } = null!;
2020

21+
[Required]
22+
[JsonPropertyName("data")]
23+
public ICollection<ResourceDataInResponse<TResource>> Data { get; set; } = null!;
24+
2125
[JsonPropertyName("included")]
2226
public IList<ResourceData> Included { get; set; } = null!;
2327

src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceIdentifierCollectionResponseDocument.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
99

1010
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
11-
internal sealed class ResourceIdentifierCollectionResponseDocument<TResource> : ManyData<ResourceIdentifier<TResource>>
11+
internal sealed class ResourceIdentifierCollectionResponseDocument<TResource>
1212
where TResource : IIdentifiable
1313
{
1414
[JsonPropertyName("jsonapi")]
@@ -18,6 +18,10 @@ internal sealed class ResourceIdentifierCollectionResponseDocument<TResource> :
1818
[JsonPropertyName("links")]
1919
public ResourceIdentifierCollectionTopLevelLinks Links { get; set; } = null!;
2020

21+
[Required]
22+
[JsonPropertyName("data")]
23+
public ICollection<ResourceIdentifier<TResource>> Data { get; set; } = null!;
24+
2125
[JsonPropertyName("meta")]
2226
public IDictionary<string, object> Meta { get; set; } = null!;
2327
}

src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceIdentifierResponseDocument.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
99

1010
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
11-
internal sealed class ResourceIdentifierResponseDocument<TResource> : SingleData<ResourceIdentifier<TResource>>
11+
internal sealed class ResourceIdentifierResponseDocument<TResource>
1212
where TResource : IIdentifiable
1313
{
1414
[JsonPropertyName("jsonapi")]
@@ -18,6 +18,10 @@ internal sealed class ResourceIdentifierResponseDocument<TResource> : SingleData
1818
[JsonPropertyName("links")]
1919
public ResourceIdentifierTopLevelLinks Links { get; set; } = null!;
2020

21+
[Required]
22+
[JsonPropertyName("data")]
23+
public ResourceIdentifier<TResource> Data { get; set; } = null!;
24+
2125
[JsonPropertyName("meta")]
2226
public IDictionary<string, object> Meta { get; set; } = null!;
2327
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Text.Json.Serialization;
13
using JetBrains.Annotations;
24
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
35
using JsonApiDotNetCore.Resources;
46

57
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
68

79
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
8-
internal sealed class ResourcePatchRequestDocument<TResource> : SingleData<ResourceDataInPatchRequest<TResource>>
9-
where TResource : IIdentifiable;
10+
internal sealed class ResourcePatchRequestDocument<TResource>
11+
where TResource : IIdentifiable
12+
{
13+
[Required]
14+
[JsonPropertyName("data")]
15+
public ResourceDataInPatchRequest<TResource> Data { get; set; } = null!;
16+
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Text.Json.Serialization;
13
using JetBrains.Annotations;
24
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
35
using JsonApiDotNetCore.Resources;
46

57
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
68

79
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
8-
internal sealed class ResourcePostRequestDocument<TResource> : SingleData<ResourceDataInPostRequest<TResource>>
9-
where TResource : IIdentifiable;
10+
internal sealed class ResourcePostRequestDocument<TResource>
11+
where TResource : IIdentifiable
12+
{
13+
[Required]
14+
[JsonPropertyName("data")]
15+
public ResourceDataInPostRequest<TResource> Data { get; set; } = null!;
16+
}

src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/SecondaryResourceResponseDocument.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
99

1010
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
11-
internal sealed class SecondaryResourceResponseDocument<TResource> : SingleData<ResourceDataInResponse<TResource>>
11+
internal sealed class SecondaryResourceResponseDocument<TResource>
1212
where TResource : IIdentifiable
1313
{
1414
[JsonPropertyName("jsonapi")]
@@ -18,6 +18,10 @@ internal sealed class SecondaryResourceResponseDocument<TResource> : SingleData<
1818
[JsonPropertyName("links")]
1919
public ResourceTopLevelLinks Links { get; set; } = null!;
2020

21+
[Required]
22+
[JsonPropertyName("data")]
23+
public ResourceDataInResponse<TResource> Data { get; set; } = null!;
24+
2125
[JsonPropertyName("included")]
2226
public IList<ResourceData> Included { get; set; } = null!;
2327

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/JsonApiObjects/ManyData.cs

-15
This file was deleted.

src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NullableSingleData.cs

-15
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Text.Json.Serialization;
13
using JetBrains.Annotations;
24
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
35
using JsonApiDotNetCore.Resources;
46

57
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
68

79
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
8-
internal sealed class NullableToOneRelationshipInRequest<TResource> : NullableSingleData<ResourceIdentifier<TResource>>
9-
where TResource : IIdentifiable;
10+
internal sealed class NullableToOneRelationshipInRequest<TResource>
11+
where TResource : IIdentifiable
12+
{
13+
[Required]
14+
[JsonPropertyName("data")]
15+
public ResourceIdentifier<TResource>? Data { get; set; }
16+
}

src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/NullableToOneRelationshipInResponse.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.ComponentModel.DataAnnotations;
12
using System.Text.Json.Serialization;
23
using JetBrains.Annotations;
34
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Links;
@@ -7,13 +8,17 @@
78
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
89

910
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
10-
internal sealed class NullableToOneRelationshipInResponse<TResource> : NullableSingleData<ResourceIdentifier<TResource>>
11+
internal sealed class NullableToOneRelationshipInResponse<TResource>
1112
where TResource : IIdentifiable
1213
{
1314
// Non-required because the related controller may be unavailable when used in an include.
1415
[JsonPropertyName("links")]
1516
public RelationshipLinks Links { get; set; } = null!;
1617

18+
[Required]
19+
[JsonPropertyName("data")]
20+
public ResourceIdentifier<TResource>? Data { get; set; }
21+
1722
[JsonPropertyName("meta")]
1823
public IDictionary<string, object> Meta { get; set; } = null!;
1924
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Text.Json.Serialization;
13
using JetBrains.Annotations;
24
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
35
using JsonApiDotNetCore.Resources;
46

57
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
68

79
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
8-
internal sealed class ToManyRelationshipInRequest<TResource> : ManyData<ResourceIdentifier<TResource>>
9-
where TResource : IIdentifiable;
10+
internal sealed class ToManyRelationshipInRequest<TResource>
11+
where TResource : IIdentifiable
12+
{
13+
[Required]
14+
[JsonPropertyName("data")]
15+
public ICollection<ResourceIdentifier<TResource>> Data { get; set; } = null!;
16+
}

src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToManyRelationshipInResponse.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.ComponentModel.DataAnnotations;
12
using System.Text.Json.Serialization;
23
using JetBrains.Annotations;
34
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Links;
@@ -7,13 +8,17 @@
78
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
89

910
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
10-
internal sealed class ToManyRelationshipInResponse<TResource> : ManyData<ResourceIdentifier<TResource>>
11+
internal sealed class ToManyRelationshipInResponse<TResource>
1112
where TResource : IIdentifiable
1213
{
1314
// Non-required because the related controller may be unavailable when used in an include.
1415
[JsonPropertyName("links")]
1516
public RelationshipLinks Links { get; set; } = null!;
1617

18+
[Required]
19+
[JsonPropertyName("data")]
20+
public ICollection<ResourceIdentifier<TResource>> Data { get; set; } = null!;
21+
1722
[JsonPropertyName("meta")]
1823
public IDictionary<string, object> Meta { get; set; } = null!;
1924
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Text.Json.Serialization;
13
using JetBrains.Annotations;
24
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
35
using JsonApiDotNetCore.Resources;
46

57
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
68

79
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
8-
internal sealed class ToOneRelationshipInRequest<TResource> : SingleData<ResourceIdentifier<TResource>>
9-
where TResource : IIdentifiable;
10+
internal sealed class ToOneRelationshipInRequest<TResource>
11+
where TResource : IIdentifiable
12+
{
13+
[Required]
14+
[JsonPropertyName("data")]
15+
public ResourceIdentifier<TResource> Data { get; set; } = null!;
16+
}

0 commit comments

Comments
 (0)