Skip to content

Refactor Data properties in component schemas #1522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 30, 2024
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 @@ -7,10 +7,10 @@

namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;

// 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
// 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
// imply this property is required. Instead, we use [Required] explicitly, because this is how Swashbuckle is instructed to mark properties as required.
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
internal sealed class NullableResourceIdentifierResponseDocument<TResource> : NullableSingleData<ResourceIdentifier<TResource>>
internal sealed class NullableResourceIdentifierResponseDocument<TResource>
where TResource : IIdentifiable
{
[JsonPropertyName("jsonapi")]
Expand All @@ -20,6 +20,10 @@ internal sealed class NullableResourceIdentifierResponseDocument<TResource> : Nu
[JsonPropertyName("links")]
public ResourceIdentifierTopLevelLinks Links { get; set; } = null!;

[Required]
[JsonPropertyName("data")]
public ResourceIdentifier<TResource>? Data { get; set; }

[JsonPropertyName("meta")]
public IDictionary<string, object> Meta { get; set; } = null!;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;

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

[Required]
[JsonPropertyName("data")]
public ResourceDataInResponse<TResource>? Data { get; set; }

[JsonPropertyName("included")]
public IList<ResourceData> Included { get; set; } = null!;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;

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

[Required]
[JsonPropertyName("data")]
public ResourceDataInResponse<TResource> Data { get; set; } = null!;

[JsonPropertyName("included")]
public IList<ResourceData> Included { get; set; } = null!;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;

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

[Required]
[JsonPropertyName("data")]
public ICollection<ResourceDataInResponse<TResource>> Data { get; set; } = null!;

[JsonPropertyName("included")]
public IList<ResourceData> Included { get; set; } = null!;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;

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

[Required]
[JsonPropertyName("data")]
public ICollection<ResourceIdentifier<TResource>> Data { get; set; } = null!;

[JsonPropertyName("meta")]
public IDictionary<string, object> Meta { get; set; } = null!;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;

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

[Required]
[JsonPropertyName("data")]
public ResourceIdentifier<TResource> Data { get; set; } = null!;

[JsonPropertyName("meta")]
public IDictionary<string, object> Meta { get; set; } = null!;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
using JsonApiDotNetCore.Resources;

namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;

[UsedImplicitly(ImplicitUseTargetFlags.Members)]
internal sealed class ResourcePatchRequestDocument<TResource> : SingleData<ResourceDataInPatchRequest<TResource>>
where TResource : IIdentifiable;
internal sealed class ResourcePatchRequestDocument<TResource>
where TResource : IIdentifiable
{
[Required]
[JsonPropertyName("data")]
public ResourceDataInPatchRequest<TResource> Data { get; set; } = null!;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
using JsonApiDotNetCore.Resources;

namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;

[UsedImplicitly(ImplicitUseTargetFlags.Members)]
internal sealed class ResourcePostRequestDocument<TResource> : SingleData<ResourceDataInPostRequest<TResource>>
where TResource : IIdentifiable;
internal sealed class ResourcePostRequestDocument<TResource>
where TResource : IIdentifiable
{
[Required]
[JsonPropertyName("data")]
public ResourceDataInPostRequest<TResource> Data { get; set; } = null!;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;

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

[Required]
[JsonPropertyName("data")]
public ResourceDataInResponse<TResource> Data { get; set; } = null!;

[JsonPropertyName("included")]
public IList<ResourceData> Included { get; set; } = null!;

Expand Down
73 changes: 73 additions & 0 deletions src/JsonApiDotNetCore.OpenApi/JsonApiObjects/JsonApiSchemaFacts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;

#pragma warning disable AV1008 // Class should not be static

namespace JsonApiDotNetCore.OpenApi.JsonApiObjects;

internal static class JsonApiSchemaFacts
{
private static readonly Type[] RequestSchemaTypes =
[
typeof(ResourcePostRequestDocument<>),
typeof(ResourcePatchRequestDocument<>),
typeof(ToOneRelationshipInRequest<>),
typeof(NullableToOneRelationshipInRequest<>),
typeof(ToManyRelationshipInRequest<>)
];

private static readonly Type[] ResponseSchemaTypes =
[
typeof(ResourceCollectionResponseDocument<>),
typeof(PrimaryResourceResponseDocument<>),
typeof(SecondaryResourceResponseDocument<>),
typeof(NullableSecondaryResourceResponseDocument<>),
typeof(ResourceIdentifierCollectionResponseDocument<>),
typeof(ResourceIdentifierResponseDocument<>),
typeof(NullableResourceIdentifierResponseDocument<>),
typeof(ErrorResponseDocument)
];

private static readonly Type[] SchemaTypesHavingNullableDataProperty =
[
typeof(NullableSecondaryResourceResponseDocument<>),
typeof(NullableResourceIdentifierResponseDocument<>),
typeof(NullableToOneRelationshipInRequest<>),
typeof(NullableToOneRelationshipInResponse<>)
];

private static readonly Type[] RelationshipInResponseSchemaTypes =
[
typeof(ToOneRelationshipInResponse<>),
typeof(ToManyRelationshipInResponse<>),
typeof(NullableToOneRelationshipInResponse<>)
];

public static bool RequiresCustomSchemaGenerator(Type schemaType)
{
// Subset of the entry types Swashbuckle calls us for, which must be handled by our custom schema generators.

Type lookupType = schemaType.ConstructedToOpenType();
return RequestSchemaTypes.Contains(lookupType) || ResponseSchemaTypes.Contains(lookupType);
}

public static bool IsRequestSchemaType(Type schemaType)
{
Type lookupType = schemaType.ConstructedToOpenType();
return RequestSchemaTypes.Contains(lookupType);
}

public static bool HasNullableDataProperty(Type schemaType)
{
// Swashbuckle infers non-nullable because our Data properties are [Required].

Type lookupType = schemaType.ConstructedToOpenType();
return SchemaTypesHavingNullableDataProperty.Contains(lookupType);
}

public static bool IsRelationshipInResponseType(Type schemaType)
{
Type lookupType = schemaType.ConstructedToOpenType();
return RelationshipInResponseSchemaTypes.Contains(lookupType);
}
}
15 changes: 0 additions & 15 deletions src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ManyData.cs

This file was deleted.

15 changes: 0 additions & 15 deletions src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NullableSingleData.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
using JsonApiDotNetCore.Resources;

namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;

[UsedImplicitly(ImplicitUseTargetFlags.Members)]
internal sealed class NullableToOneRelationshipInRequest<TResource> : NullableSingleData<ResourceIdentifier<TResource>>
where TResource : IIdentifiable;
internal sealed class NullableToOneRelationshipInRequest<TResource>
where TResource : IIdentifiable
{
[Required]
[JsonPropertyName("data")]
public ResourceIdentifier<TResource>? Data { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Links;
Expand All @@ -7,13 +8,17 @@
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;

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

[Required]
[JsonPropertyName("data")]
public ResourceIdentifier<TResource>? Data { get; set; }

[JsonPropertyName("meta")]
public IDictionary<string, object> Meta { get; set; } = null!;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
using JsonApiDotNetCore.Resources;

namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;

[UsedImplicitly(ImplicitUseTargetFlags.Members)]
internal sealed class ToManyRelationshipInRequest<TResource> : ManyData<ResourceIdentifier<TResource>>
where TResource : IIdentifiable;
internal sealed class ToManyRelationshipInRequest<TResource>
where TResource : IIdentifiable
{
[Required]
[JsonPropertyName("data")]
public ICollection<ResourceIdentifier<TResource>> Data { get; set; } = null!;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Links;
Expand All @@ -7,13 +8,17 @@
namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;

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

[Required]
[JsonPropertyName("data")]
public ICollection<ResourceIdentifier<TResource>> Data { get; set; } = null!;

[JsonPropertyName("meta")]
public IDictionary<string, object> Meta { get; set; } = null!;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
using JsonApiDotNetCore.Resources;

namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;

[UsedImplicitly(ImplicitUseTargetFlags.Members)]
internal sealed class ToOneRelationshipInRequest<TResource> : SingleData<ResourceIdentifier<TResource>>
where TResource : IIdentifiable;
internal sealed class ToOneRelationshipInRequest<TResource>
where TResource : IIdentifiable
{
[Required]
[JsonPropertyName("data")]
public ResourceIdentifier<TResource> Data { get; set; } = null!;
}
Loading