Skip to content

Merge master (v5.6.0) into openapi #1585

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 9 commits into from
Jun 30, 2024
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@
<WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodingGuidelines.ruleset</CodeAnalysisRuleSet>
<RunSettingsFilePath>$(MSBuildThisFileDirectory)tests.runsettings</RunSettingsFilePath>
<JsonApiDotNetCoreVersionPrefix>5.5.2</JsonApiDotNetCoreVersionPrefix>
<JsonApiDotNetCoreVersionPrefix>5.6.1</JsonApiDotNetCoreVersionPrefix>
</PropertyGroup>
</Project>
7 changes: 4 additions & 3 deletions src/Examples/DapperExample/Repositories/DapperRepository.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using Dapper;
using DapperExample.AtomicOperations;
using DapperExample.TranslationToSql;
Expand Down Expand Up @@ -177,7 +178,7 @@ public async Task<int> CountAsync(FilterExpression? filter, CancellationToken ca
}

/// <inheritdoc />
public Task<TResource> GetForCreateAsync(Type resourceClrType, TId id, CancellationToken cancellationToken)
public Task<TResource> GetForCreateAsync(Type resourceClrType, [DisallowNull] TId id, CancellationToken cancellationToken)
{
ArgumentGuard.NotNull(resourceClrType);

Expand Down Expand Up @@ -355,7 +356,7 @@ await ExecuteInTransactionAsync(async transaction =>
}

/// <inheritdoc />
public async Task DeleteAsync(TResource? resourceFromDatabase, TId id, CancellationToken cancellationToken)
public async Task DeleteAsync(TResource? resourceFromDatabase, [DisallowNull] TId id, CancellationToken cancellationToken)
{
TResource placeholderResource = resourceFromDatabase ?? _resourceFactory.CreateInstance<TResource>();
placeholderResource.Id = id;
Expand Down Expand Up @@ -451,7 +452,7 @@ await ExecuteInTransactionAsync(async transaction =>
}

/// <inheritdoc />
public async Task AddToToManyRelationshipAsync(TResource? leftResource, TId leftId, ISet<IIdentifiable> rightResourceIds,
public async Task AddToToManyRelationshipAsync(TResource? leftResource, [DisallowNull] TId leftId, ISet<IIdentifiable> rightResourceIds,
CancellationToken cancellationToken)
{
ArgumentGuard.NotNull(rightResourceIds);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Errors;
using JsonApiDotNetCore.Queries;
Expand Down Expand Up @@ -114,7 +115,7 @@ private bool SetPrimaryTotalCountIsZero()
}

/// <inheritdoc />
public Task<TResource> GetAsync(TId id, CancellationToken cancellationToken)
public Task<TResource> GetAsync([DisallowNull] TId id, CancellationToken cancellationToken)
{
QueryLayer queryLayer = _queryLayerComposer.ComposeForGetById(id, _resourceType, TopFieldSelection.PreserveExisting);

Expand All @@ -124,14 +125,14 @@ public Task<TResource> GetAsync(TId id, CancellationToken cancellationToken)

if (resource == null)
{
throw new ResourceNotFoundException(id!.ToString()!, _resourceType.PublicName);
throw new ResourceNotFoundException(id.ToString()!, _resourceType.PublicName);
}

return Task.FromResult(resource);
}

/// <inheritdoc />
public Task<object?> GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken)
public Task<object?> GetSecondaryAsync([DisallowNull] TId id, string relationshipName, CancellationToken cancellationToken)
{
RelationshipAttribute? relationship = _resourceType.FindRelationshipByPublicName(relationshipName);

Expand All @@ -151,7 +152,7 @@ public Task<TResource> GetAsync(TId id, CancellationToken cancellationToken)

if (primaryResource == null)
{
throw new ResourceNotFoundException(id!.ToString()!, _resourceType.PublicName);
throw new ResourceNotFoundException(id.ToString()!, _resourceType.PublicName);
}

object? rightValue = relationship.GetValue(primaryResource);
Expand All @@ -164,7 +165,7 @@ public Task<TResource> GetAsync(TId id, CancellationToken cancellationToken)
return Task.FromResult(rightValue);
}

private void SetNonPrimaryTotalCount(TId id, RelationshipAttribute relationship)
private void SetNonPrimaryTotalCount([DisallowNull] TId id, RelationshipAttribute relationship)
{
if (_options.IncludeTotalResourceCount && relationship is HasManyAttribute hasManyRelationship)
{
Expand All @@ -188,7 +189,7 @@ private void SetNonPrimaryTotalCount(TId id, RelationshipAttribute relationship)
}

/// <inheritdoc />
public Task<object?> GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken)
public Task<object?> GetRelationshipAsync([DisallowNull] TId id, string relationshipName, CancellationToken cancellationToken)
{
return GetSecondaryAsync(id, relationshipName, cancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public AddToRelationshipProcessor(IAddToRelationshipService<TResource, TId> serv
var leftId = (TId)operation.Resource.GetTypedId();
ISet<IIdentifiable> rightResourceIds = operation.GetSecondaryResources();

await _service.AddToToManyRelationshipAsync(leftId, operation.Request.Relationship!.PublicName, rightResourceIds, cancellationToken);
await _service.AddToToManyRelationshipAsync(leftId!, operation.Request.Relationship!.PublicName, rightResourceIds, cancellationToken);

return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public DeleteProcessor(IDeleteService<TResource, TId> service)
ArgumentGuard.NotNull(operation);

var id = (TId)operation.Resource.GetTypedId();
await _service.DeleteAsync(id, cancellationToken);
await _service.DeleteAsync(id!, cancellationToken);

return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public RemoveFromRelationshipProcessor(IRemoveFromRelationshipService<TResource,
var leftId = (TId)operation.Resource.GetTypedId();
ISet<IIdentifiable> rightResourceIds = operation.GetSecondaryResources();

await _service.RemoveFromToManyRelationshipAsync(leftId, operation.Request.Relationship!.PublicName, rightResourceIds, cancellationToken);
await _service.RemoveFromToManyRelationshipAsync(leftId!, operation.Request.Relationship!.PublicName, rightResourceIds, cancellationToken);

return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public SetRelationshipProcessor(ISetRelationshipService<TResource, TId> service)
var leftId = (TId)operation.Resource.GetTypedId();
object? rightValue = GetRelationshipRightValue(operation);

await _service.SetRelationshipAsync(leftId, operation.Request.Relationship!.PublicName, rightValue, cancellationToken);
await _service.SetRelationshipAsync(leftId!, operation.Request.Relationship!.PublicName, rightValue, cancellationToken);

return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public UpdateProcessor(IUpdateService<TResource, TId> service)
ArgumentGuard.NotNull(operation);

var resource = (TResource)operation.Resource;
TResource? updated = await _service.UpdateAsync(resource.Id, resource, cancellationToken);
TResource? updated = await _service.UpdateAsync(resource.Id!, resource, cancellationToken);

return updated == null ? null : operation.WithResource(updated);
}
Expand Down
34 changes: 23 additions & 11 deletions src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using System.Diagnostics.CodeAnalysis;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Errors;
using JsonApiDotNetCore.Middleware;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -106,7 +109,7 @@ public virtual async Task<IActionResult> GetAsync(CancellationToken cancellation
/// GET /articles/1 HTTP/1.1
/// ]]></code>
/// </summary>
public virtual async Task<IActionResult> GetAsync(TId id, CancellationToken cancellationToken)
public virtual async Task<IActionResult> GetAsync([DisallowNull] TId id, CancellationToken cancellationToken)
{
_traceWriter.LogMethodStart(new
{
Expand All @@ -131,7 +134,7 @@ public virtual async Task<IActionResult> GetAsync(TId id, CancellationToken canc
/// GET /articles/1/revisions HTTP/1.1
/// ]]></code>
/// </summary>
public virtual async Task<IActionResult> GetSecondaryAsync(TId id, string relationshipName, CancellationToken cancellationToken)
public virtual async Task<IActionResult> GetSecondaryAsync([DisallowNull] TId id, string relationshipName, CancellationToken cancellationToken)
{
_traceWriter.LogMethodStart(new
{
Expand Down Expand Up @@ -160,7 +163,7 @@ public virtual async Task<IActionResult> GetSecondaryAsync(TId id, string relati
/// GET /articles/1/relationships/revisions HTTP/1.1
/// ]]></code>
/// </summary>
public virtual async Task<IActionResult> GetRelationshipAsync(TId id, string relationshipName, CancellationToken cancellationToken)
public virtual async Task<IActionResult> GetRelationshipAsync([DisallowNull] TId id, string relationshipName, CancellationToken cancellationToken)
{
_traceWriter.LogMethodStart(new
{
Expand Down Expand Up @@ -207,7 +210,7 @@ public virtual async Task<IActionResult> PostAsync([FromBody] TResource resource
TResource? newResource = await _create.CreateAsync(resource, cancellationToken);

string resourceId = (newResource ?? resource).StringId!;
string locationUrl = HttpContext.Request.Path.Add($"/{resourceId}");
string locationUrl = GetLocationUrl(resourceId);

if (newResource == null)
{
Expand All @@ -218,6 +221,15 @@ public virtual async Task<IActionResult> PostAsync([FromBody] TResource resource
return Created(locationUrl, newResource);
}

private string GetLocationUrl(string resourceId)
{
PathString locationPath = HttpContext.Request.Path.Add($"/{resourceId}");

return _options.UseRelativeLinks
? UriHelper.BuildRelative(HttpContext.Request.PathBase, locationPath)
: UriHelper.BuildAbsolute(HttpContext.Request.Scheme, HttpContext.Request.Host, HttpContext.Request.PathBase, locationPath);
}

/// <summary>
/// Adds resources to a to-many relationship. Example: <code><![CDATA[
/// POST /articles/1/revisions HTTP/1.1
Expand All @@ -235,8 +247,8 @@ public virtual async Task<IActionResult> PostAsync([FromBody] TResource resource
/// <param name="cancellationToken">
/// Propagates notification that request handling should be canceled.
/// </param>
public virtual async Task<IActionResult> PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet<IIdentifiable> rightResourceIds,
CancellationToken cancellationToken)
public virtual async Task<IActionResult> PostRelationshipAsync([DisallowNull] TId id, string relationshipName,
[FromBody] ISet<IIdentifiable> rightResourceIds, CancellationToken cancellationToken)
{
_traceWriter.LogMethodStart(new
{
Expand Down Expand Up @@ -264,7 +276,7 @@ public virtual async Task<IActionResult> PostRelationshipAsync(TId id, string re
/// PATCH /articles/1 HTTP/1.1
/// ]]></code>
/// </summary>
public virtual async Task<IActionResult> PatchAsync(TId id, [FromBody] TResource resource, CancellationToken cancellationToken)
public virtual async Task<IActionResult> PatchAsync([DisallowNull] TId id, [FromBody] TResource resource, CancellationToken cancellationToken)
{
_traceWriter.LogMethodStart(new
{
Expand Down Expand Up @@ -310,7 +322,7 @@ public virtual async Task<IActionResult> PatchAsync(TId id, [FromBody] TResource
/// <param name="cancellationToken">
/// Propagates notification that request handling should be canceled.
/// </param>
public virtual async Task<IActionResult> PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object? rightValue,
public virtual async Task<IActionResult> PatchRelationshipAsync([DisallowNull] TId id, string relationshipName, [FromBody] object? rightValue,
CancellationToken cancellationToken)
{
_traceWriter.LogMethodStart(new
Expand All @@ -337,7 +349,7 @@ public virtual async Task<IActionResult> PatchRelationshipAsync(TId id, string r
/// DELETE /articles/1 HTTP/1.1
/// ]]></code>
/// </summary>
public virtual async Task<IActionResult> DeleteAsync(TId id, CancellationToken cancellationToken)
public virtual async Task<IActionResult> DeleteAsync([DisallowNull] TId id, CancellationToken cancellationToken)
{
_traceWriter.LogMethodStart(new
{
Expand Down Expand Up @@ -371,8 +383,8 @@ public virtual async Task<IActionResult> DeleteAsync(TId id, CancellationToken c
/// <param name="cancellationToken">
/// Propagates notification that request handling should be canceled.
/// </param>
public virtual async Task<IActionResult> DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet<IIdentifiable> rightResourceIds,
CancellationToken cancellationToken)
public virtual async Task<IActionResult> DeleteRelationshipAsync([DisallowNull] TId id, string relationshipName,
[FromBody] ISet<IIdentifiable> rightResourceIds, CancellationToken cancellationToken)
{
_traceWriter.LogMethodStart(new
{
Expand Down
21 changes: 12 additions & 9 deletions src/JsonApiDotNetCore/Controllers/JsonApiController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Services;
Expand Down Expand Up @@ -50,23 +51,25 @@ public override async Task<IActionResult> GetAsync(CancellationToken cancellatio
/// <inheritdoc />
[HttpGet("{id}")]
[HttpHead("{id}")]
public override async Task<IActionResult> GetAsync([Required] TId id, CancellationToken cancellationToken)
public override async Task<IActionResult> GetAsync([Required] [DisallowNull] TId id, CancellationToken cancellationToken)
{
return await base.GetAsync(id, cancellationToken);
}

/// <inheritdoc />
[HttpGet("{id}/{relationshipName}")]
[HttpHead("{id}/{relationshipName}")]
public override async Task<IActionResult> GetSecondaryAsync([Required] TId id, [Required] string relationshipName, CancellationToken cancellationToken)
public override async Task<IActionResult> GetSecondaryAsync([Required] [DisallowNull] TId id, [Required] string relationshipName,
CancellationToken cancellationToken)
{
return await base.GetSecondaryAsync(id, relationshipName, cancellationToken);
}

/// <inheritdoc />
[HttpGet("{id}/relationships/{relationshipName}")]
[HttpHead("{id}/relationships/{relationshipName}")]
public override async Task<IActionResult> GetRelationshipAsync([Required] TId id, [Required] string relationshipName, CancellationToken cancellationToken)
public override async Task<IActionResult> GetRelationshipAsync([Required] [DisallowNull] TId id, [Required] string relationshipName,
CancellationToken cancellationToken)
{
return await base.GetRelationshipAsync(id, relationshipName, cancellationToken);
}
Expand All @@ -80,38 +83,38 @@ public override async Task<IActionResult> PostAsync([Required] TResource resourc

/// <inheritdoc />
[HttpPost("{id}/relationships/{relationshipName}")]
public override async Task<IActionResult> PostRelationshipAsync([Required] TId id, [Required] string relationshipName,
public override async Task<IActionResult> PostRelationshipAsync([Required] [DisallowNull] TId id, [Required] string relationshipName,
[Required] ISet<IIdentifiable> rightResourceIds, CancellationToken cancellationToken)
{
return await base.PostRelationshipAsync(id, relationshipName, rightResourceIds, cancellationToken);
}

/// <inheritdoc />
[HttpPatch("{id}")]
public override async Task<IActionResult> PatchAsync([Required] TId id, [Required] TResource resource, CancellationToken cancellationToken)
public override async Task<IActionResult> PatchAsync([Required] [DisallowNull] TId id, [Required] TResource resource, CancellationToken cancellationToken)
{
return await base.PatchAsync(id, resource, cancellationToken);
}

/// <inheritdoc />
[HttpPatch("{id}/relationships/{relationshipName}")]
// Parameter `[Required] object? rightValue` makes Swashbuckle generate the OpenAPI request body as required. We don't actually validate ModelState, so it doesn't hurt.
public override async Task<IActionResult> PatchRelationshipAsync([Required] TId id, [Required] string relationshipName, [Required] object? rightValue,
CancellationToken cancellationToken)
public override async Task<IActionResult> PatchRelationshipAsync([Required] [DisallowNull] TId id, [Required] string relationshipName,
[Required] object? rightValue, CancellationToken cancellationToken)
{
return await base.PatchRelationshipAsync(id, relationshipName, rightValue, cancellationToken);
}

/// <inheritdoc />
[HttpDelete("{id}")]
public override async Task<IActionResult> DeleteAsync([Required] TId id, CancellationToken cancellationToken)
public override async Task<IActionResult> DeleteAsync([Required] [DisallowNull] TId id, CancellationToken cancellationToken)
{
return await base.DeleteAsync(id, cancellationToken);
}

/// <inheritdoc />
[HttpDelete("{id}/relationships/{relationshipName}")]
public override async Task<IActionResult> DeleteRelationshipAsync([Required] TId id, [Required] string relationshipName,
public override async Task<IActionResult> DeleteRelationshipAsync([Required] [DisallowNull] TId id, [Required] string relationshipName,
[Required] ISet<IIdentifiable> rightResourceIds, CancellationToken cancellationToken)
{
return await base.DeleteRelationshipAsync(id, relationshipName, rightResourceIds, cancellationToken);
Expand Down
Loading
Loading