Skip to content

Commit

Permalink
Add error messages to forbidden and unauthorized (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ahammdan committed May 25, 2024
1 parent dce276b commit 43f0b4c
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 7 deletions.
48 changes: 46 additions & 2 deletions src/Ardalis.Result.AspNetCore/MinimalApiResultExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Linq;
using System.Text;

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

Expand Down Expand Up @@ -36,8 +38,8 @@ internal static Microsoft.AspNetCore.Http.IResult ToMinimalApiResult(this IResul
ResultStatus.Ok => result is Result ? Results.Ok() : Results.Ok(result.GetValue()),
ResultStatus.Created => Results.Created("", result.GetValue()),
ResultStatus.NotFound => NotFoundEntity(result),
ResultStatus.Unauthorized => Results.Unauthorized(),
ResultStatus.Forbidden => Results.Forbid(),
ResultStatus.Unauthorized => UnAuthorized(result),
ResultStatus.Forbidden => Forbidden(result),
ResultStatus.Invalid => Results.BadRequest(result.ValidationErrors),
ResultStatus.Error => UnprocessableEntity(result),
ResultStatus.Conflict => ConflictEntity(result),
Expand Down Expand Up @@ -140,5 +142,47 @@ private static Microsoft.AspNetCore.Http.IResult UnavailableEntity(IResult resul
return Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
}

private static Microsoft.AspNetCore.Http.IResult Forbidden(IResult result)
{
var details = new StringBuilder("Next error(s) occurred:");

if (result.Errors.Any())
{
foreach (var error in result.Errors) details.Append("* ").Append(error).AppendLine();

return Results.Problem(new ProblemDetails
{
Title = "Forbidden.",
Detail = details.ToString(),
Status = StatusCodes.Status403Forbidden
});
}
else
{
return Results.Forbid();
}
}

private static Microsoft.AspNetCore.Http.IResult UnAuthorized(IResult result)
{
var details = new StringBuilder("Next error(s) occurred:");

if (result.Errors.Any())
{
foreach (var error in result.Errors) details.Append("* ").Append(error).AppendLine();

return Results.Problem(new ProblemDetails
{
Title = "Unauthorized.",
Detail = details.ToString(),
Status = StatusCodes.Status401Unauthorized
});
}
else
{
return Results.Unauthorized();
}
}
}
#endif
24 changes: 23 additions & 1 deletion src/Ardalis.Result/Result.Void.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ public static Result Error(string errorMessage)
return new Result(ResultStatus.Forbidden);
}

/// <summary>
/// The parameters to the call were correct, but the user does not have permission to perform some action.
/// See also HTTP 403 Forbidden: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors
/// </summary>
/// <param name="errorMessages">A list of string error messages.</param>
/// <returns>A Result</returns>
public new static Result Forbidden(params string[] errorMessages)
{
return new Result(ResultStatus.Forbidden) { Errors = errorMessages };
}

/// <summary>
/// This is similar to Forbidden, but should be used when the user has not authenticated or has attempted to authenticate but failed.
/// See also HTTP 401 Unauthorized: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors
Expand All @@ -145,7 +156,18 @@ public static Result Error(string errorMessage)
{
return new Result(ResultStatus.Unauthorized);
}


/// <summary>
/// This is similar to Forbidden, but should be used when the user has not authenticated or has attempted to authenticate but failed.
/// See also HTTP 401 Unauthorized: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors
/// </summary>
/// <param name="errorMessages">A list of string error messages.</param>
/// <returns>A Result</returns>
public new static Result Unauthorized(params string[] errorMessages)
{
return new Result(ResultStatus.Unauthorized) { Errors = errorMessages };
}

/// <summary>
/// Represents a situation where a service is in conflict due to the current state of a resource,
/// such as an edit conflict between multiple concurrent updates.
Expand Down
24 changes: 23 additions & 1 deletion src/Ardalis.Result/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,17 @@ public static Result<T> Forbidden()
return new Result<T>(ResultStatus.Forbidden);
}

/// <summary>
/// The parameters to the call were correct, but the user does not have permission to perform some action.
/// See also HTTP 403 Forbidden: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors
/// </summary>
/// <param name="errorMessages">A list of string error messages.</param>
/// <returns>A Result<typeparamref name="T"/></returns>
public static Result<T> Forbidden(params string[] errorMessages)
{
return new Result<T>(ResultStatus.Forbidden) { Errors = errorMessages };
}

/// <summary>
/// This is similar to Forbidden, but should be used when the user has not authenticated or has attempted to authenticate but failed.
/// See also HTTP 401 Unauthorized: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors
Expand All @@ -226,7 +237,18 @@ public static Result<T> Unauthorized()
{
return new Result<T>(ResultStatus.Unauthorized);
}


/// <summary>
/// This is similar to Forbidden, but should be used when the user has not authenticated or has attempted to authenticate but failed.
/// See also HTTP 401 Unauthorized: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors
/// </summary>
/// <param name="errorMessages">A list of string error messages.</param>
/// <returns>A Result<typeparamref name="T"/></returns>
public static Result<T> Unauthorized(params string[] errorMessages)
{
return new Result<T>(ResultStatus.Unauthorized) { Errors = errorMessages };
}

/// <summary>
/// Represents a situation where a service is in conflict due to the current state of a resource,
/// such as an edit conflict between multiple concurrent updates.
Expand Down
8 changes: 6 additions & 2 deletions src/Ardalis.Result/ResultExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ public static Result<TDestination> Map<TSource, TDestination>(this Result<TSourc
case ResultStatus.NotFound: return result.Errors.Any()
? Result<TDestination>.NotFound(result.Errors.ToArray())
: Result<TDestination>.NotFound();
case ResultStatus.Unauthorized: return Result<TDestination>.Unauthorized();
case ResultStatus.Forbidden: return Result<TDestination>.Forbidden();
case ResultStatus.Unauthorized: return result.Errors.Any()
? Result<TDestination>.Unauthorized(result.Errors.ToArray())
: Result<TDestination>.Unauthorized();
case ResultStatus.Forbidden: return result.Errors.Any()
? Result<TDestination>.Forbidden(result.Errors.ToArray())
: Result<TDestination>.Forbidden();
case ResultStatus.Invalid: return Result<TDestination>.Invalid(result.ValidationErrors);
case ResultStatus.Error: return Result<TDestination>.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId));
case ResultStatus.Conflict: return result.Errors.Any()
Expand Down
28 changes: 28 additions & 0 deletions tests/Ardalis.Result.UnitTests/ResultConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,34 @@ public void InitializesStatusToForbiddenGivenForbiddenFactoryCall()
Assert.Equal(ResultStatus.Forbidden, result.Status);
}

[Fact]
public void InitializesStatusToForbiddenGivenForbiddenFactoryCallWithString()
{
var errorMessage = "You are forbidden";
var result = Result<object>.Forbidden(errorMessage);

result.Status.Should().Be(ResultStatus.Forbidden);
result.Errors.Single().Should().Be(errorMessage);
}

[Fact]
public void InitializesStatusToUnauthorizedGivenUnauthorizedFactoryCall()
{
var result = Result<object>.Unauthorized();

Assert.Equal(ResultStatus.Unauthorized, result.Status);
}

[Fact]
public void InitializesStatusToUnauthorizedGivenUnauthorizedFactoryCallWithString()
{
var errorMessage = "You are unauthorized";
var result = Result<object>.Unauthorized(errorMessage);

result.Status.Should().Be(ResultStatus.Unauthorized);
result.Errors.Single().Should().Be(errorMessage);
}

[Fact]
public void InitializesStatusToUnavailableGivenUnavailableFactoryCallWithString()
{
Expand Down
24 changes: 24 additions & 0 deletions tests/Ardalis.Result.UnitTests/ResultMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,30 @@ public void ShouldProduceCriticalErrorWithError()
actual.Errors.Single().Should().Be(expectedMessage);
}

[Fact]
public void ShouldProduceForbiddenWithError()
{
string expectedMessage = "You are forbidden";
var result = Result<int>.Forbidden(expectedMessage);

var actual = result.Map(val => val.ToString());

actual.Status.Should().Be(ResultStatus.Forbidden);
actual.Errors.Single().Should().Be(expectedMessage);
}

[Fact]
public void ShouldProduceUnauthroizedWithError()
{
string expectedMessage = "You are unauthorized";
var result = Result<int>.Unauthorized(expectedMessage);

var actual = result.Map(val => val.ToString());

actual.Status.Should().Be(ResultStatus.Unauthorized);
actual.Errors.Single().Should().Be(expectedMessage);
}

[Fact]
public void ShouldProductNoContentWithoutAnyContent()
{
Expand Down
24 changes: 23 additions & 1 deletion tests/Ardalis.Result.UnitTests/ResultVoidConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,17 @@ public void InitializesForbiddenResultWithFactoryMethod()
Assert.Equal(ResultStatus.Forbidden, result.Status);
}

[Fact]
public void InitializesStatusToForbiddenGivenForbiddenFactoryCallWithString()
{
var errorMessage = "You are forbidden";
var result = Result<object>.Forbidden(errorMessage);

Assert.Null(result.Value);
result.Status.Should().Be(ResultStatus.Forbidden);
result.Errors.Single().Should().Be(errorMessage);
}

[Fact]
public void InitializesUnauthorizedResultWithFactoryMethod()
{
Expand All @@ -151,7 +162,18 @@ public void InitializesUnauthorizedResultWithFactoryMethod()
Assert.Null(result.Value);
Assert.Equal(ResultStatus.Unauthorized, result.Status);
}


[Fact]
public void InitializesUnauthorizedResultWithFactoryMethodWithString()
{
var errorMessage = "You are unauthorized";
var result = Result.Unauthorized(errorMessage);

result.Value.Should().BeNull();
result.Status.Should().Be(ResultStatus.Unauthorized);
result.Errors.Single().Should().Be(errorMessage);
}

[Fact]
public void InitializesConflictResultWithFactoryMethod()
{
Expand Down

0 comments on commit 43f0b4c

Please sign in to comment.