diff --git a/docs/in-depth/server/index.md b/docs/in-depth/server/index.md index d5210d91..c36e2f4b 100644 --- a/docs/in-depth/server/index.md +++ b/docs/in-depth/server/index.md @@ -130,7 +130,7 @@ The options you can set include: * `PageSize` (int, default: 100) is the maximum number of items a query operation returns in a single page. * `MaxTop` (int, default: 512000) is the maximum number of items a user can request in a single operation. * `EnableSoftDelete` (bool, default: false) enables soft-delete, which marks items as deleted instead of deleting them from the database. Soft delete allows clients to update their offline cache, but requires that deleted items are purged from the database separately. -* `UnauthorizedStatusCode` (int, default: 401 Unauthorized) is the status code returned when the user isn't allowed to do an action. +* `UnauthorizedStatusCode` (int, default: 401 Unauthorized) is the status code returned when the user isn't allowed to do an action. The value must be a client error (4xx) status code in the range 400-499. ## Configure access permissions diff --git a/src/CommunityToolkit.Datasync.Server/Models/TableControllerOptions.cs b/src/CommunityToolkit.Datasync.Server/Models/TableControllerOptions.cs index ca23c1e7..f6435b2f 100644 --- a/src/CommunityToolkit.Datasync.Server/Models/TableControllerOptions.cs +++ b/src/CommunityToolkit.Datasync.Server/Models/TableControllerOptions.cs @@ -13,6 +13,7 @@ public class TableControllerOptions { private int _pageSize = 100; private int _maxTop = MAX_TOP; + private int _unauthorizedStatusCode = StatusCodes.Status401Unauthorized; /// /// The maximum page size that can be specified by the server. @@ -73,5 +74,19 @@ public int PageSize /// /// The status code returned when the user is not authorized to perform an operation. /// - public int UnauthorizedStatusCode { get; set; } = StatusCodes.Status401Unauthorized; + /// + /// The value must be a client error (4xx) status code in the range 400-499. Setting a value + /// outside this range (for example, a success or server error code) is considered a + /// misconfiguration and throws . + /// + public int UnauthorizedStatusCode + { + get => this._unauthorizedStatusCode; + set + { + ArgumentOutOfRangeException.ThrowIfLessThan(value, 400, nameof(UnauthorizedStatusCode)); + ArgumentOutOfRangeException.ThrowIfGreaterThan(value, 499, nameof(UnauthorizedStatusCode)); + this._unauthorizedStatusCode = value; + } + } } diff --git a/tests/CommunityToolkit.Datasync.Server.Test/Models/TableControllerOptions_Tests.cs b/tests/CommunityToolkit.Datasync.Server.Test/Models/TableControllerOptions_Tests.cs index 1fa1fc21..00d57587 100644 --- a/tests/CommunityToolkit.Datasync.Server.Test/Models/TableControllerOptions_Tests.cs +++ b/tests/CommunityToolkit.Datasync.Server.Test/Models/TableControllerOptions_Tests.cs @@ -33,14 +33,38 @@ public void Ctor_NoNegativeNumbers(int pageSize, int maxTop) act.Should().Throw(); } + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(200)] + [InlineData(399)] + [InlineData(500)] + [InlineData(510)] + public void Ctor_InvalidUnauthorizedStatusCode_Throws(int statusCode) + { + Action act = () => _ = new TableControllerOptions { UnauthorizedStatusCode = statusCode }; + act.Should().Throw(); + } + + [Theory] + [InlineData(400)] + [InlineData(401)] + [InlineData(403)] + [InlineData(499)] + public void Ctor_ValidUnauthorizedStatusCode_Roundtrips(int statusCode) + { + TableControllerOptions sut = new() { UnauthorizedStatusCode = statusCode }; + sut.UnauthorizedStatusCode.Should().Be(statusCode); + } + [Fact] public void Ctor_Roundtrips() { - TableControllerOptions sut = new() { EnableSoftDelete = true, MaxTop = 100, PageSize = 50, UnauthorizedStatusCode = 510 }; + TableControllerOptions sut = new() { EnableSoftDelete = true, MaxTop = 100, PageSize = 50, UnauthorizedStatusCode = 403 }; sut.EnableSoftDelete.Should().BeTrue(); sut.MaxTop.Should().Be(100); sut.PageSize.Should().Be(50); - sut.UnauthorizedStatusCode.Should().Be(510); + sut.UnauthorizedStatusCode.Should().Be(403); } }