Skip to content
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

Update openapi branch #1105

Merged
merged 28 commits into from
Nov 23, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fb0c5fc
Empty commit to republish docs from master branch
Oct 18, 2021
758a191
Redesign conversion between JSON objects and ASP.NET models (#1091)
Oct 27, 2021
4a2abe9
Remove short-hand interfaces for TId is int (#1093)
Oct 28, 2021
f0d638f
Nullable reference types (#1095)
Nov 3, 2021
edfcd8c
Abort build when install failed
Nov 3, 2021
1c2313a
Downgrade docfx due to published corrupt zip
Nov 3, 2021
4798144
Secondary paging (#1100)
Nov 4, 2021
b8f63e5
Add None enum member to flags
Nov 4, 2021
fb8913f
Merge master
maurei Nov 4, 2021
b88d39e
fix broken build
Nov 4, 2021
78a4e9d
Resource Graph validations (#1101)
Nov 8, 2021
40f6826
- Replaced ResourceContext with ResourceType and calls to ResourceTyp…
maurei Nov 8, 2021
ee9f2eb
removed JsonApiObjectNullabilityProcessor and enabled NRT support in …
maurei Nov 8, 2021
7b78e99
Remove unused members
maurei Nov 8, 2021
fb5102f
Merge branch 'master' into master-into-openapi
maurei Nov 8, 2021
67b39e7
Merge branch 'openapi' into master-into-openapi
maurei Nov 9, 2021
b7930de
Added authors element to NuGet package spec
Nov 9, 2021
1b01e52
Added .NET 6 support to roadmap
Nov 15, 2021
e07903c
Process review feedback
maurei Nov 16, 2021
8a6bfac
Fix code cleanup (#1107)
Nov 18, 2021
a1b0a7b
Process review feedback
maurei Nov 19, 2021
ae17e01
Various fixes and improvements (#1114)
Nov 22, 2021
15f5923
Fixed: swapped method names
Nov 22, 2021
a6e11be
- Renamed ExpansibleEndpointMetadata to NonPrimaryEndpointMetadata
maurei Nov 22, 2021
1bdb70a
Process review feedback
maurei Nov 22, 2021
17fed9b
Rephrase
maurei Nov 22, 2021
8694e6a
Merge branch 'master' into master-into-openapi
Nov 23, 2021
d50719b
Update version prefix after merge
Nov 23, 2021
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 @@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
using JsonApiDotNetCore.OpenApi.JsonApiMetadata;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -26,15 +25,14 @@ internal sealed class JsonApiActionDescriptorCollectionProvider : IActionDescrip

public ActionDescriptorCollection ActionDescriptors => GetActionDescriptors();

public JsonApiActionDescriptorCollectionProvider(IResourceGraph resourceGraph, IControllerResourceMapping controllerResourceMapping,
public JsonApiActionDescriptorCollectionProvider(IControllerResourceMapping controllerResourceMapping,
IActionDescriptorCollectionProvider defaultProvider)
{
ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
ArgumentGuard.NotNull(controllerResourceMapping, nameof(controllerResourceMapping));
ArgumentGuard.NotNull(defaultProvider, nameof(defaultProvider));

_defaultProvider = defaultProvider;
_jsonApiEndpointMetadataProvider = new JsonApiEndpointMetadataProvider(resourceGraph, controllerResourceMapping);
_jsonApiEndpointMetadataProvider = new JsonApiEndpointMetadataProvider(controllerResourceMapping);
}

private ActionDescriptorCollection GetActionDescriptors()
Expand Down Expand Up @@ -67,27 +65,26 @@ private static bool IsVisibleJsonApiEndpoint(ActionDescriptor descriptor)
return descriptor is ControllerActionDescriptor controllerAction && controllerAction.Properties.ContainsKey(typeof(ApiDescriptionActionData));
}

private static IList<ActionDescriptor> AddJsonApiMetadataToAction(ActionDescriptor endpoint, IJsonApiEndpointMetadata? jsonApiEndpointMetadata)
private static IEnumerable<ActionDescriptor> AddJsonApiMetadataToAction(ActionDescriptor endpoint, IJsonApiEndpointMetadata? jsonApiEndpointMetadata)
{
switch (jsonApiEndpointMetadata)
{
case PrimaryResponseMetadata primaryMetadata:
{
UpdateProducesResponseTypeAttribute(endpoint, primaryMetadata.Type);
UpdateProducesResponseTypeAttribute(endpoint, primaryMetadata.DocumentType);
return Array.Empty<ActionDescriptor>();
}
case PrimaryRequestMetadata primaryMetadata:
{
UpdateBodyParameterDescriptor(endpoint, primaryMetadata.Type);
UpdateBodyParameterDescriptor(endpoint, primaryMetadata.DocumentType);
return Array.Empty<ActionDescriptor>();
}
case ExpansibleEndpointMetadata expansibleMetadata
when expansibleMetadata is RelationshipResponseMetadata || expansibleMetadata is SecondaryResponseMetadata:
case ExpansibleEndpointMetadata expansibleMetadata and (RelationshipResponseMetadata or SecondaryResponseMetadata):
{
return Expand(endpoint, expansibleMetadata,
(expandedEndpoint, relationshipType, _) => UpdateProducesResponseTypeAttribute(expandedEndpoint, relationshipType));
(expandedEndpoint, documentType, _) => UpdateProducesResponseTypeAttribute(expandedEndpoint, documentType));
}
case ExpansibleEndpointMetadata expansibleMetadata when expansibleMetadata is RelationshipRequestMetadata:
case ExpansibleEndpointMetadata expansibleMetadata and RelationshipRequestMetadata:
{
return Expand(endpoint, expansibleMetadata, UpdateBodyParameterDescriptor);
}
Expand All @@ -98,56 +95,56 @@ private static IList<ActionDescriptor> AddJsonApiMetadataToAction(ActionDescript
}
}

private static void UpdateProducesResponseTypeAttribute(ActionDescriptor endpoint, Type responseTypeToSet)
private static void UpdateProducesResponseTypeAttribute(ActionDescriptor endpoint, Type responseDocumentType)
{
if (ProducesJsonApiResponseBody(endpoint))
if (ProducesJsonApiResponseDocument(endpoint))
{
var producesResponse = endpoint.GetFilterMetadata<ProducesResponseTypeAttribute>();

if (producesResponse != null)
{
producesResponse.Type = responseTypeToSet;

producesResponse.Type = responseDocumentType;
return;
}
}

throw new UnreachableCodeException();
}

private static bool ProducesJsonApiResponseBody(ActionDescriptor endpoint)
private static bool ProducesJsonApiResponseDocument(ActionDescriptor endpoint)
{
var produces = endpoint.GetFilterMetadata<ProducesAttribute>();

return produces != null && produces.ContentTypes.Any(contentType => contentType == HeaderConstants.MediaType);
}

private static IList<ActionDescriptor> Expand(ActionDescriptor genericEndpoint, ExpansibleEndpointMetadata metadata,
private static IEnumerable<ActionDescriptor> Expand(ActionDescriptor genericEndpoint, ExpansibleEndpointMetadata metadata,
Action<ActionDescriptor, Type, string> expansionCallback)
{
var expansion = new List<ActionDescriptor>();

foreach ((string relationshipName, Type relationshipType) in metadata.ExpansionElements)
foreach ((string relationshipName, Type documentType) in metadata.DocumentTypesByRelationshipName)
{
ActionDescriptor expandedEndpoint = Clone(genericEndpoint);
RemovePathParameter(expandedEndpoint.Parameters, JsonApiPathParameter.RelationshipName);

if (expandedEndpoint.AttributeRouteInfo == null)
if (genericEndpoint.AttributeRouteInfo == null)
{
throw new NotSupportedException("Only attribute based routing is supported for JsonApiDotNetCore endpoints");
throw new NotSupportedException("Only attribute routing is supported for JsonApiDotNetCore endpoints.");
}

ExpandTemplate(expandedEndpoint.AttributeRouteInfo, relationshipName);
ActionDescriptor expandedEndpoint = Clone(genericEndpoint);

RemovePathParameter(expandedEndpoint.Parameters, JsonApiPathParameter.RelationshipName);

ExpandTemplate(expandedEndpoint.AttributeRouteInfo!, relationshipName);
bart-degreed marked this conversation as resolved.
Show resolved Hide resolved

expansionCallback(expandedEndpoint, relationshipType, relationshipName);
expansionCallback(expandedEndpoint, documentType, relationshipName);

expansion.Add(expandedEndpoint);
}

return expansion;
}

private static void UpdateBodyParameterDescriptor(ActionDescriptor endpoint, Type bodyType, string? parameterName = null)
private static void UpdateBodyParameterDescriptor(ActionDescriptor endpoint, Type documentType, string? parameterName = null)
{
ControllerParameterDescriptor? requestBodyDescriptor = endpoint.GetBodyParameterDescriptor();

Expand All @@ -157,8 +154,8 @@ private static void UpdateBodyParameterDescriptor(ActionDescriptor endpoint, Typ
throw new UnreachableCodeException();
}

requestBodyDescriptor.ParameterType = bodyType;
ParameterInfo replacementParameterInfo = requestBodyDescriptor.ParameterInfo.WithParameterType(bodyType);
requestBodyDescriptor.ParameterType = documentType;
ParameterInfo replacementParameterInfo = requestBodyDescriptor.ParameterInfo.WithParameterType(documentType);

if (parameterName != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiMetadata
{
internal abstract class ExpansibleEndpointMetadata
{
public abstract IDictionary<string, Type> ExpansionElements { get; }
public IDictionary<string, Type> DocumentTypesByRelationshipName { get; }

protected ExpansibleEndpointMetadata(IDictionary<string, Type> documentTypesByRelationshipName)
{
ArgumentGuard.NotNull(documentTypesByRelationshipName, nameof(documentTypesByRelationshipName));

DocumentTypesByRelationshipName = documentTypesByRelationshipName;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
using System.Reflection;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
using JsonApiDotNetCore.OpenApi.JsonApiObjects;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
using JsonApiDotNetCore.OpenApi.JsonApiObjects.RelationshipData;
using JsonApiDotNetCore.Resources.Annotations;

namespace JsonApiDotNetCore.OpenApi.JsonApiMetadata
Expand All @@ -16,16 +16,14 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiMetadata
/// </summary>
internal sealed class JsonApiEndpointMetadataProvider
{
private readonly IResourceGraph _resourceGraph;
private readonly IControllerResourceMapping _controllerResourceMapping;
private readonly EndpointResolver _endpointResolver = new();
private readonly NonPrimaryDocumentTypeFactory _nonPrimaryDocumentTypeFactory = new();

public JsonApiEndpointMetadataProvider(IResourceGraph resourceGraph, IControllerResourceMapping controllerResourceMapping)
public JsonApiEndpointMetadataProvider(IControllerResourceMapping controllerResourceMapping)
{
ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
ArgumentGuard.NotNull(controllerResourceMapping, nameof(controllerResourceMapping));

_resourceGraph = resourceGraph;
_controllerResourceMapping = controllerResourceMapping;
}

Expand All @@ -47,28 +45,28 @@ public JsonApiEndpointMetadataContainer Get(MethodInfo controllerAction)
throw new UnreachableCodeException();
}

IJsonApiRequestMetadata? requestMetadata = GetRequestMetadata(endpoint.Value, primaryResourceType.ClrType);
IJsonApiResponseMetadata? responseMetadata = GetResponseMetadata(endpoint.Value, primaryResourceType.ClrType);
IJsonApiRequestMetadata? requestMetadata = GetRequestMetadata(endpoint.Value, primaryResourceType);
IJsonApiResponseMetadata? responseMetadata = GetResponseMetadata(endpoint.Value, primaryResourceType);
return new JsonApiEndpointMetadataContainer(requestMetadata, responseMetadata);
}

private IJsonApiRequestMetadata? GetRequestMetadata(JsonApiEndpoint endpoint, Type primaryResourceType)
private IJsonApiRequestMetadata? GetRequestMetadata(JsonApiEndpoint endpoint, ResourceType primaryResourceType)
{
switch (endpoint)
{
case JsonApiEndpoint.Post:
{
return GetPostRequestMetadata(primaryResourceType);
return GetPostRequestMetadata(primaryResourceType.ClrType);
}
case JsonApiEndpoint.Patch:
{
return GetPatchRequestMetadata(primaryResourceType);
return GetPatchRequestMetadata(primaryResourceType.ClrType);
}
case JsonApiEndpoint.PostRelationship:
case JsonApiEndpoint.PatchRelationship:
case JsonApiEndpoint.DeleteRelationship:
{
return GetRelationshipRequestMetadata(primaryResourceType, endpoint != JsonApiEndpoint.PatchRelationship);
return GetRelationshipRequestMetadata(primaryResourceType.Relationships, endpoint != JsonApiEndpoint.PatchRelationship);
}
default:
{
Expand All @@ -77,38 +75,31 @@ public JsonApiEndpointMetadataContainer Get(MethodInfo controllerAction)
}
}

private static PrimaryRequestMetadata GetPostRequestMetadata(Type primaryResourceType)
private static PrimaryRequestMetadata GetPostRequestMetadata(Type resourceClrType)
{
Type documentType = typeof(ResourcePostRequestDocument<>).MakeGenericType(primaryResourceType);
Type documentType = typeof(ResourcePostRequestDocument<>).MakeGenericType(resourceClrType);

return new PrimaryRequestMetadata(documentType);
}

private static PrimaryRequestMetadata GetPatchRequestMetadata(Type primaryResourceType)
private static PrimaryRequestMetadata GetPatchRequestMetadata(Type resourceClrType)
{
Type documentType = typeof(ResourcePatchRequestDocument<>).MakeGenericType(primaryResourceType);
Type documentType = typeof(ResourcePatchRequestDocument<>).MakeGenericType(resourceClrType);

return new PrimaryRequestMetadata(documentType);
}

private RelationshipRequestMetadata GetRelationshipRequestMetadata(Type primaryResourceType, bool ignoreHasOneRelationships)
private RelationshipRequestMetadata GetRelationshipRequestMetadata(IEnumerable<RelationshipAttribute> relationships, bool ignoreHasOneRelationships)
{
IEnumerable<RelationshipAttribute> relationships = _resourceGraph.GetResourceType(primaryResourceType).Relationships;
IEnumerable<RelationshipAttribute> relationshipsOfEndpoint = ignoreHasOneRelationships ? relationships.OfType<HasManyAttribute>() : relationships;

if (ignoreHasOneRelationships)
{
relationships = relationships.OfType<HasManyAttribute>();
}

IDictionary<string, Type> resourceTypesByRelationshipName = relationships.ToDictionary(relationship => relationship.PublicName,
relationship => relationship is HasManyAttribute
? typeof(ToManyRelationshipRequestData<>).MakeGenericType(relationship.RightType.ClrType)
: typeof(ToOneRelationshipRequestData<>).MakeGenericType(relationship.RightType.ClrType));
IDictionary<string, Type> requestDocumentTypesByRelationshipName = relationshipsOfEndpoint.ToDictionary(relationship => relationship.PublicName,
_nonPrimaryDocumentTypeFactory.GetForRelationshipRequest);

return new RelationshipRequestMetadata(resourceTypesByRelationshipName);
return new RelationshipRequestMetadata(requestDocumentTypesByRelationshipName);
}

private IJsonApiResponseMetadata? GetResponseMetadata(JsonApiEndpoint endpoint, Type primaryResourceType)
private IJsonApiResponseMetadata? GetResponseMetadata(JsonApiEndpoint endpoint, ResourceType primaryResourceType)
{
switch (endpoint)
{
Expand All @@ -117,15 +108,15 @@ private RelationshipRequestMetadata GetRelationshipRequestMetadata(Type primaryR
case JsonApiEndpoint.Post:
case JsonApiEndpoint.Patch:
{
return GetPrimaryResponseMetadata(primaryResourceType, endpoint == JsonApiEndpoint.GetCollection);
return GetPrimaryResponseMetadata(primaryResourceType.ClrType, endpoint == JsonApiEndpoint.GetCollection);
}
case JsonApiEndpoint.GetSecondary:
{
return GetSecondaryResponseMetadata(primaryResourceType);
return GetSecondaryResponseMetadata(primaryResourceType.Relationships);
}
case JsonApiEndpoint.GetRelationship:
{
return GetRelationshipResponseMetadata(primaryResourceType);
return GetRelationshipResponseMetadata(primaryResourceType.Relationships);
}
default:
{
Expand All @@ -134,44 +125,28 @@ private RelationshipRequestMetadata GetRelationshipRequestMetadata(Type primaryR
}
}

private static PrimaryResponseMetadata GetPrimaryResponseMetadata(Type primaryResourceType, bool endpointReturnsCollection)
private static PrimaryResponseMetadata GetPrimaryResponseMetadata(Type resourceClrType, bool endpointReturnsCollection)
{
Type documentOpenType = endpointReturnsCollection ? typeof(ResourceCollectionResponseDocument<>) : typeof(PrimaryResourceResponseDocument<>);
Type documentType = documentOpenType.MakeGenericType(primaryResourceType);
Type documentType = documentOpenType.MakeGenericType(resourceClrType);

return new PrimaryResponseMetadata(documentType);
}

private SecondaryResponseMetadata GetSecondaryResponseMetadata(Type primaryResourceType)
{
IDictionary<string, Type> responseTypesByRelationshipName = GetMetadataByRelationshipName(primaryResourceType, relationship =>
{
Type documentType = relationship is HasManyAttribute
? typeof(ResourceCollectionResponseDocument<>)
: typeof(SecondaryResourceResponseDocument<>);

return documentType.MakeGenericType(relationship.RightType.ClrType);
});

return new SecondaryResponseMetadata(responseTypesByRelationshipName);
}

private IDictionary<string, Type> GetMetadataByRelationshipName(Type primaryResourceType,
Func<RelationshipAttribute, Type> extractRelationshipMetadataCallback)
private SecondaryResponseMetadata GetSecondaryResponseMetadata(IEnumerable<RelationshipAttribute> relationships)
{
IReadOnlyCollection<RelationshipAttribute> relationships = _resourceGraph.GetResourceType(primaryResourceType).Relationships;
IDictionary<string, Type> responseDocumentTypesByRelationshipName = relationships.ToDictionary(relationship => relationship.PublicName,
_nonPrimaryDocumentTypeFactory.GetForSecondaryResponse);

return relationships.ToDictionary(relationship => relationship.PublicName, extractRelationshipMetadataCallback);
return new SecondaryResponseMetadata(responseDocumentTypesByRelationshipName);
}

private RelationshipResponseMetadata GetRelationshipResponseMetadata(Type primaryResourceType)
private RelationshipResponseMetadata GetRelationshipResponseMetadata(IEnumerable<RelationshipAttribute> relationships)
{
IDictionary<string, Type> responseTypesByRelationshipName = GetMetadataByRelationshipName(primaryResourceType,
relationship => relationship is HasManyAttribute
? typeof(ResourceIdentifierCollectionResponseDocument<>).MakeGenericType(relationship.RightType.ClrType)
: typeof(ResourceIdentifierResponseDocument<>).MakeGenericType(relationship.RightType.ClrType));
IDictionary<string, Type> responseDocumentTypesByRelationshipName = relationships.ToDictionary(relationship => relationship.PublicName,
_nonPrimaryDocumentTypeFactory.GetForRelationshipResponse);

return new RelationshipResponseMetadata(responseTypesByRelationshipName);
return new RelationshipResponseMetadata(responseDocumentTypesByRelationshipName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiMetadata
{
internal sealed class PrimaryRequestMetadata : IJsonApiRequestMetadata
{
public Type Type { get; }
public Type DocumentType { get; }

public PrimaryRequestMetadata(Type type)
public PrimaryRequestMetadata(Type documentType)
{
ArgumentGuard.NotNull(type, nameof(type));
ArgumentGuard.NotNull(documentType, nameof(documentType));

Type = type;
DocumentType = documentType;
}
}
}
Loading