Skip to content

Commit bce1d8f

Browse files
committed
Fix usage of whitespace IDs when TId is string
1 parent 9dc1c69 commit bce1d8f

File tree

6 files changed

+692
-15
lines changed

6 files changed

+692
-15
lines changed

src/JsonApiDotNetCore/Controllers/JsonApiController.cs

+14-12
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,15 @@ public override async Task<IActionResult> GetAsync(CancellationToken cancellatio
5151
/// <inheritdoc />
5252
[HttpGet("{id}")]
5353
[HttpHead("{id}")]
54-
public override async Task<IActionResult> GetAsync([Required] [DisallowNull] TId id, CancellationToken cancellationToken)
54+
public override async Task<IActionResult> GetAsync([Required(AllowEmptyStrings = true)] [DisallowNull] TId id, CancellationToken cancellationToken)
5555
{
5656
return await base.GetAsync(id, cancellationToken);
5757
}
5858

5959
/// <inheritdoc />
6060
[HttpGet("{id}/{relationshipName}")]
6161
[HttpHead("{id}/{relationshipName}")]
62-
public override async Task<IActionResult> GetSecondaryAsync([Required] [DisallowNull] TId id, [Required] string relationshipName,
62+
public override async Task<IActionResult> GetSecondaryAsync([Required(AllowEmptyStrings = true)] [DisallowNull] TId id, [Required] string relationshipName,
6363
CancellationToken cancellationToken)
6464
{
6565
return await base.GetSecondaryAsync(id, relationshipName, cancellationToken);
@@ -68,8 +68,8 @@ public override async Task<IActionResult> GetSecondaryAsync([Required] [Disallow
6868
/// <inheritdoc />
6969
[HttpGet("{id}/relationships/{relationshipName}")]
7070
[HttpHead("{id}/relationships/{relationshipName}")]
71-
public override async Task<IActionResult> GetRelationshipAsync([Required] [DisallowNull] TId id, [Required] string relationshipName,
72-
CancellationToken cancellationToken)
71+
public override async Task<IActionResult> GetRelationshipAsync([Required(AllowEmptyStrings = true)] [DisallowNull] TId id,
72+
[Required] string relationshipName, CancellationToken cancellationToken)
7373
{
7474
return await base.GetRelationshipAsync(id, relationshipName, cancellationToken);
7575
}
@@ -83,39 +83,41 @@ public override async Task<IActionResult> PostAsync([Required] TResource resourc
8383

8484
/// <inheritdoc />
8585
[HttpPost("{id}/relationships/{relationshipName}")]
86-
public override async Task<IActionResult> PostRelationshipAsync([Required] [DisallowNull] TId id, [Required] string relationshipName,
87-
[Required] ISet<IIdentifiable> rightResourceIds, CancellationToken cancellationToken)
86+
public override async Task<IActionResult> PostRelationshipAsync([Required(AllowEmptyStrings = true)] [DisallowNull] TId id,
87+
[Required] string relationshipName, [Required] ISet<IIdentifiable> rightResourceIds, CancellationToken cancellationToken)
8888
{
8989
return await base.PostRelationshipAsync(id, relationshipName, rightResourceIds, cancellationToken);
9090
}
9191

9292
/// <inheritdoc />
9393
[HttpPatch("{id}")]
94-
public override async Task<IActionResult> PatchAsync([Required] [DisallowNull] TId id, [Required] TResource resource, CancellationToken cancellationToken)
94+
public override async Task<IActionResult> PatchAsync([Required(AllowEmptyStrings = true)] [DisallowNull] TId id, [Required] TResource resource,
95+
CancellationToken cancellationToken)
9596
{
9697
return await base.PatchAsync(id, resource, cancellationToken);
9798
}
9899

99100
/// <inheritdoc />
100101
[HttpPatch("{id}/relationships/{relationshipName}")]
102+
// `AllowEmptyStrings = true` in `[Required]` prevents the model binder from producing a validation error on whitespace when TId is string.
101103
// Parameter `[Required] object? rightValue` makes Swashbuckle generate the OpenAPI request body as required. We don't actually validate ModelState, so it doesn't hurt.
102-
public override async Task<IActionResult> PatchRelationshipAsync([Required] [DisallowNull] TId id, [Required] string relationshipName,
103-
[Required] object? rightValue, CancellationToken cancellationToken)
104+
public override async Task<IActionResult> PatchRelationshipAsync([Required(AllowEmptyStrings = true)] [DisallowNull] TId id,
105+
[Required] string relationshipName, [Required] object? rightValue, CancellationToken cancellationToken)
104106
{
105107
return await base.PatchRelationshipAsync(id, relationshipName, rightValue, cancellationToken);
106108
}
107109

108110
/// <inheritdoc />
109111
[HttpDelete("{id}")]
110-
public override async Task<IActionResult> DeleteAsync([Required] [DisallowNull] TId id, CancellationToken cancellationToken)
112+
public override async Task<IActionResult> DeleteAsync([Required(AllowEmptyStrings = true)] [DisallowNull] TId id, CancellationToken cancellationToken)
111113
{
112114
return await base.DeleteAsync(id, cancellationToken);
113115
}
114116

115117
/// <inheritdoc />
116118
[HttpDelete("{id}/relationships/{relationshipName}")]
117-
public override async Task<IActionResult> DeleteRelationshipAsync([Required] [DisallowNull] TId id, [Required] string relationshipName,
118-
[Required] ISet<IIdentifiable> rightResourceIds, CancellationToken cancellationToken)
119+
public override async Task<IActionResult> DeleteRelationshipAsync([Required(AllowEmptyStrings = true)] [DisallowNull] TId id,
120+
[Required] string relationshipName, [Required] ISet<IIdentifiable> rightResourceIds, CancellationToken cancellationToken)
119121
{
120122
return await base.DeleteRelationshipAsync(id, relationshipName, rightResourceIds, cancellationToken);
121123
}

test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/EmptyGuidAsKeyTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -572,9 +572,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
572572

573573
await _testContext.RunOnDatabaseAsync(async dbContext =>
574574
{
575-
Map? gameInDatabase = await dbContext.Maps.FirstWithIdOrDefaultAsync(existingMap.Id);
575+
Map? mapInDatabase = await dbContext.Maps.FirstWithIdOrDefaultAsync(existingMap.Id);
576576

577-
gameInDatabase.Should().BeNull();
577+
mapInDatabase.Should().BeNull();
578578
});
579579
}
580580
}

test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/Game.cs

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ public sealed class Game : Identifiable<int?>
1717
[Attr]
1818
public Guid SessionToken => Guid.NewGuid();
1919

20+
[HasOne]
21+
public Player? Host { get; set; }
22+
2023
[HasMany]
2124
public ICollection<Player> ActivePlayers { get; set; } = new List<Player>();
2225

test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/Player.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
using JetBrains.Annotations;
2+
using JsonApiDotNetCore.Configuration;
23
using JsonApiDotNetCore.Resources;
34
using JsonApiDotNetCore.Resources.Annotations;
45

56
namespace JsonApiDotNetCoreTests.IntegrationTests.ZeroKeys;
67

78
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
8-
[Resource(ControllerNamespace = "JsonApiDotNetCoreTests.IntegrationTests.ZeroKeys")]
9+
[Resource(ControllerNamespace = "JsonApiDotNetCoreTests.IntegrationTests.ZeroKeys", ClientIdGeneration = ClientIdGenerationMode.Allowed)]
910
public sealed class Player : Identifiable<string?>
1011
{
1112
[Attr]

0 commit comments

Comments
 (0)