Skip to content

Commit 43f0b4c

Browse files
authored
Add error messages to forbidden and unauthorized (#190)
1 parent dce276b commit 43f0b4c

File tree

7 files changed

+173
-7
lines changed

7 files changed

+173
-7
lines changed

src/Ardalis.Result.AspNetCore/MinimalApiResultExtensions.cs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Linq;
33
using System.Text;
4+
5+
using Microsoft.AspNetCore.Authentication;
46
using Microsoft.AspNetCore.Http;
57
using Microsoft.AspNetCore.Mvc;
68

@@ -36,8 +38,8 @@ internal static Microsoft.AspNetCore.Http.IResult ToMinimalApiResult(this IResul
3638
ResultStatus.Ok => result is Result ? Results.Ok() : Results.Ok(result.GetValue()),
3739
ResultStatus.Created => Results.Created("", result.GetValue()),
3840
ResultStatus.NotFound => NotFoundEntity(result),
39-
ResultStatus.Unauthorized => Results.Unauthorized(),
40-
ResultStatus.Forbidden => Results.Forbid(),
41+
ResultStatus.Unauthorized => UnAuthorized(result),
42+
ResultStatus.Forbidden => Forbidden(result),
4143
ResultStatus.Invalid => Results.BadRequest(result.ValidationErrors),
4244
ResultStatus.Error => UnprocessableEntity(result),
4345
ResultStatus.Conflict => ConflictEntity(result),
@@ -140,5 +142,47 @@ private static Microsoft.AspNetCore.Http.IResult UnavailableEntity(IResult resul
140142
return Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
141143
}
142144
}
145+
146+
private static Microsoft.AspNetCore.Http.IResult Forbidden(IResult result)
147+
{
148+
var details = new StringBuilder("Next error(s) occurred:");
149+
150+
if (result.Errors.Any())
151+
{
152+
foreach (var error in result.Errors) details.Append("* ").Append(error).AppendLine();
153+
154+
return Results.Problem(new ProblemDetails
155+
{
156+
Title = "Forbidden.",
157+
Detail = details.ToString(),
158+
Status = StatusCodes.Status403Forbidden
159+
});
160+
}
161+
else
162+
{
163+
return Results.Forbid();
164+
}
165+
}
166+
167+
private static Microsoft.AspNetCore.Http.IResult UnAuthorized(IResult result)
168+
{
169+
var details = new StringBuilder("Next error(s) occurred:");
170+
171+
if (result.Errors.Any())
172+
{
173+
foreach (var error in result.Errors) details.Append("* ").Append(error).AppendLine();
174+
175+
return Results.Problem(new ProblemDetails
176+
{
177+
Title = "Unauthorized.",
178+
Detail = details.ToString(),
179+
Status = StatusCodes.Status401Unauthorized
180+
});
181+
}
182+
else
183+
{
184+
return Results.Unauthorized();
185+
}
186+
}
143187
}
144188
#endif

src/Ardalis.Result/Result.Void.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,17 @@ public static Result Error(string errorMessage)
136136
return new Result(ResultStatus.Forbidden);
137137
}
138138

139+
/// <summary>
140+
/// The parameters to the call were correct, but the user does not have permission to perform some action.
141+
/// See also HTTP 403 Forbidden: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors
142+
/// </summary>
143+
/// <param name="errorMessages">A list of string error messages.</param>
144+
/// <returns>A Result</returns>
145+
public new static Result Forbidden(params string[] errorMessages)
146+
{
147+
return new Result(ResultStatus.Forbidden) { Errors = errorMessages };
148+
}
149+
139150
/// <summary>
140151
/// This is similar to Forbidden, but should be used when the user has not authenticated or has attempted to authenticate but failed.
141152
/// See also HTTP 401 Unauthorized: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors
@@ -145,7 +156,18 @@ public static Result Error(string errorMessage)
145156
{
146157
return new Result(ResultStatus.Unauthorized);
147158
}
148-
159+
160+
/// <summary>
161+
/// This is similar to Forbidden, but should be used when the user has not authenticated or has attempted to authenticate but failed.
162+
/// See also HTTP 401 Unauthorized: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors
163+
/// </summary>
164+
/// <param name="errorMessages">A list of string error messages.</param>
165+
/// <returns>A Result</returns>
166+
public new static Result Unauthorized(params string[] errorMessages)
167+
{
168+
return new Result(ResultStatus.Unauthorized) { Errors = errorMessages };
169+
}
170+
149171
/// <summary>
150172
/// Represents a situation where a service is in conflict due to the current state of a resource,
151173
/// such as an edit conflict between multiple concurrent updates.

src/Ardalis.Result/Result.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,17 @@ public static Result<T> Forbidden()
217217
return new Result<T>(ResultStatus.Forbidden);
218218
}
219219

220+
/// <summary>
221+
/// The parameters to the call were correct, but the user does not have permission to perform some action.
222+
/// See also HTTP 403 Forbidden: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors
223+
/// </summary>
224+
/// <param name="errorMessages">A list of string error messages.</param>
225+
/// <returns>A Result<typeparamref name="T"/></returns>
226+
public static Result<T> Forbidden(params string[] errorMessages)
227+
{
228+
return new Result<T>(ResultStatus.Forbidden) { Errors = errorMessages };
229+
}
230+
220231
/// <summary>
221232
/// This is similar to Forbidden, but should be used when the user has not authenticated or has attempted to authenticate but failed.
222233
/// See also HTTP 401 Unauthorized: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors
@@ -226,7 +237,18 @@ public static Result<T> Unauthorized()
226237
{
227238
return new Result<T>(ResultStatus.Unauthorized);
228239
}
229-
240+
241+
/// <summary>
242+
/// This is similar to Forbidden, but should be used when the user has not authenticated or has attempted to authenticate but failed.
243+
/// See also HTTP 401 Unauthorized: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors
244+
/// </summary>
245+
/// <param name="errorMessages">A list of string error messages.</param>
246+
/// <returns>A Result<typeparamref name="T"/></returns>
247+
public static Result<T> Unauthorized(params string[] errorMessages)
248+
{
249+
return new Result<T>(ResultStatus.Unauthorized) { Errors = errorMessages };
250+
}
251+
230252
/// <summary>
231253
/// Represents a situation where a service is in conflict due to the current state of a resource,
232254
/// such as an edit conflict between multiple concurrent updates.

src/Ardalis.Result/ResultExtensions.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ public static Result<TDestination> Map<TSource, TDestination>(this Result<TSourc
2525
case ResultStatus.NotFound: return result.Errors.Any()
2626
? Result<TDestination>.NotFound(result.Errors.ToArray())
2727
: Result<TDestination>.NotFound();
28-
case ResultStatus.Unauthorized: return Result<TDestination>.Unauthorized();
29-
case ResultStatus.Forbidden: return Result<TDestination>.Forbidden();
28+
case ResultStatus.Unauthorized: return result.Errors.Any()
29+
? Result<TDestination>.Unauthorized(result.Errors.ToArray())
30+
: Result<TDestination>.Unauthorized();
31+
case ResultStatus.Forbidden: return result.Errors.Any()
32+
? Result<TDestination>.Forbidden(result.Errors.ToArray())
33+
: Result<TDestination>.Forbidden();
3034
case ResultStatus.Invalid: return Result<TDestination>.Invalid(result.ValidationErrors);
3135
case ResultStatus.Error: return Result<TDestination>.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId));
3236
case ResultStatus.Conflict: return result.Errors.Any()

tests/Ardalis.Result.UnitTests/ResultConstructor.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,34 @@ public void InitializesStatusToForbiddenGivenForbiddenFactoryCall()
235235
Assert.Equal(ResultStatus.Forbidden, result.Status);
236236
}
237237

238+
[Fact]
239+
public void InitializesStatusToForbiddenGivenForbiddenFactoryCallWithString()
240+
{
241+
var errorMessage = "You are forbidden";
242+
var result = Result<object>.Forbidden(errorMessage);
243+
244+
result.Status.Should().Be(ResultStatus.Forbidden);
245+
result.Errors.Single().Should().Be(errorMessage);
246+
}
247+
248+
[Fact]
249+
public void InitializesStatusToUnauthorizedGivenUnauthorizedFactoryCall()
250+
{
251+
var result = Result<object>.Unauthorized();
252+
253+
Assert.Equal(ResultStatus.Unauthorized, result.Status);
254+
}
255+
256+
[Fact]
257+
public void InitializesStatusToUnauthorizedGivenUnauthorizedFactoryCallWithString()
258+
{
259+
var errorMessage = "You are unauthorized";
260+
var result = Result<object>.Unauthorized(errorMessage);
261+
262+
result.Status.Should().Be(ResultStatus.Unauthorized);
263+
result.Errors.Single().Should().Be(errorMessage);
264+
}
265+
238266
[Fact]
239267
public void InitializesStatusToUnavailableGivenUnavailableFactoryCallWithString()
240268
{

tests/Ardalis.Result.UnitTests/ResultMap.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,30 @@ public void ShouldProduceCriticalErrorWithError()
222222
actual.Errors.Single().Should().Be(expectedMessage);
223223
}
224224

225+
[Fact]
226+
public void ShouldProduceForbiddenWithError()
227+
{
228+
string expectedMessage = "You are forbidden";
229+
var result = Result<int>.Forbidden(expectedMessage);
230+
231+
var actual = result.Map(val => val.ToString());
232+
233+
actual.Status.Should().Be(ResultStatus.Forbidden);
234+
actual.Errors.Single().Should().Be(expectedMessage);
235+
}
236+
237+
[Fact]
238+
public void ShouldProduceUnauthroizedWithError()
239+
{
240+
string expectedMessage = "You are unauthorized";
241+
var result = Result<int>.Unauthorized(expectedMessage);
242+
243+
var actual = result.Map(val => val.ToString());
244+
245+
actual.Status.Should().Be(ResultStatus.Unauthorized);
246+
actual.Errors.Single().Should().Be(expectedMessage);
247+
}
248+
225249
[Fact]
226250
public void ShouldProductNoContentWithoutAnyContent()
227251
{

tests/Ardalis.Result.UnitTests/ResultVoidConstructor.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ public void InitializesForbiddenResultWithFactoryMethod()
143143
Assert.Equal(ResultStatus.Forbidden, result.Status);
144144
}
145145

146+
[Fact]
147+
public void InitializesStatusToForbiddenGivenForbiddenFactoryCallWithString()
148+
{
149+
var errorMessage = "You are forbidden";
150+
var result = Result<object>.Forbidden(errorMessage);
151+
152+
Assert.Null(result.Value);
153+
result.Status.Should().Be(ResultStatus.Forbidden);
154+
result.Errors.Single().Should().Be(errorMessage);
155+
}
156+
146157
[Fact]
147158
public void InitializesUnauthorizedResultWithFactoryMethod()
148159
{
@@ -151,7 +162,18 @@ public void InitializesUnauthorizedResultWithFactoryMethod()
151162
Assert.Null(result.Value);
152163
Assert.Equal(ResultStatus.Unauthorized, result.Status);
153164
}
154-
165+
166+
[Fact]
167+
public void InitializesUnauthorizedResultWithFactoryMethodWithString()
168+
{
169+
var errorMessage = "You are unauthorized";
170+
var result = Result.Unauthorized(errorMessage);
171+
172+
result.Value.Should().BeNull();
173+
result.Status.Should().Be(ResultStatus.Unauthorized);
174+
result.Errors.Single().Should().Be(errorMessage);
175+
}
176+
155177
[Fact]
156178
public void InitializesConflictResultWithFactoryMethod()
157179
{

0 commit comments

Comments
 (0)