Skip to content

Commit

Permalink
Update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
amantinband committed Jan 4, 2024
1 parent 406ea93 commit 6485ae8
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 73 deletions.
92 changes: 45 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
- [Turns into this 👇🏽](#turns-into-this--1)
- [Multiple Errors](#multiple-errors)
- [A more practical example 👷](#a-more-practical-example-)
- [Dropping the exceptions throwing logic ✈️](#dropping-the-exceptions-throwing-logic-)
- [Dropping the exceptions throwing logic 😎](#dropping-the-exceptions-throwing-logic-)
- [Usage 🛠️](#usage-️)
- [Creating an `ErrorOr<result>`](#creating-an-errororresult)
- [From Value, using implicit conversion](#from-value-using-implicit-conversion)
Expand Down Expand Up @@ -182,12 +182,12 @@ public class User
```csharp
public async Task<ErrorOr<User>> CreateUserAsync(string name)
{
if (await _userRepository.GetAsync(name) is User user)
if (await _userRepository.GetAsync(name) is not null)
{
return Error.Conflict("User already exists");
}

var errorOrUser = User.Create("Amichai");
var errorOrUser = User.Create(name);

if (errorOrUser.IsError)
{
Expand All @@ -214,30 +214,42 @@ public async Task<IActionResult> GetUser(Guid Id)
errors => ValidationProblem(errors.ToModelStateDictionary()));
}
```

Or, using `Then`/`Else`:

```csharp
[HttpGet("{id:guid}")]
public async Task<IActionResult> GetUser(Guid Id)
{
return await _mediator.Send(new GetUserQuery(Id))
.Then(user => _mapper.Map<UserResponse>(user))
.Then(userResponse => Ok(userResponse))
.Else(errors => ValidationProblem(errors.ToModelStateDictionary()));
}
```

A nice approach, is creating a static class with the expected errors. For example:

```csharp
public static partial class Errors
public static partial class UserErrors
{
public static class User
{
public static Error NotFound = Error.NotFound("User.NotFound", "User not found.");
public static Error DuplicateEmail = Error.Conflict("User.DuplicateEmail", "User with given email already exists.");
}
public static Error DuplicateEmail = Error.Conflict(
code: "User.DuplicateEmail",
description: "User with given email already exists.");
}
```

Which can later be used as following

```csharp

User newUser = ..;
if (await _userRepository.GetByEmailAsync(newUser.email) is not null)
if (await _userRepository.GetByEmailAsync(email) is not null)
{
return Errors.User.DuplicateEmail;
return UserErrors.DuplicateEmail;
}

User newUser = User.Create(email, password);
await _userRepository.AddAsync(newUser);

return newUser;
```

Expand All @@ -249,28 +261,24 @@ return createUserResult.MatchFirst(
error => error is Errors.User.DuplicateEmail ? Conflict() : InternalServerError());
```

# Dropping the exceptions throwing logic ✈️
# Dropping the exceptions throwing logic 😎

You have validation logic such as `MediatR` behaviors, you can drop the exceptions throwing logic and simply return a list of errors from the pipeline behavior

```csharp
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : IErrorOr
public class ValidationBehavior<TRequest, TResponse>(IValidator<TRequest>? validator = null)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : IErrorOr
{
private readonly IValidator<TRequest>? _validator;

public ValidationBehavior(IValidator<TRequest>? validator = null)
{
_validator = validator;
}
private readonly IValidator<TRequest>? _validator = validator;

public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (_validator == null)
if (_validator is null)
{
return await next();
}
Expand All @@ -282,25 +290,12 @@ public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TReques
return await next();
}

return TryCreateResponseFromErrors(validationResult.Errors, out var response)
? response
: throw new ValidationException(validationResult.Errors);
}
var errors = validationResult.Errors
.ConvertAll(error => Error.Validation(
code: error.PropertyName,
description: error.ErrorMessage));

private static bool TryCreateResponseFromErrors(List<ValidationFailure> validationFailures, out TResponse response)
{
List<Error> errors = validationFailures.ConvertAll(x => Error.Validation(
code: x.PropertyName,
description: x.ErrorMessage));

response = (TResponse?)typeof(TResponse)
.GetMethod(
name: nameof(ErrorOr<object>.From),
bindingAttr: BindingFlags.Static | BindingFlags.Public,
types: new[] { typeof(List<Error>) })?
.Invoke(null, new[] { errors })!;

return response is not null;
return (dynamic)errors;
}
}
```
Expand Down Expand Up @@ -597,17 +592,20 @@ public enum ErrorType
Validation,
Conflict,
NotFound,
Unauthorized,
}
```

Creating a new Error instance is done using one of the following static methods:

```csharp
public static Error Error.Failure(string code, string description);
public static Error Error.Unexpected(string code, string description);
public static Error Error.Validation(string code, string description);
public static Error Error.Conflict(string code, string description);
public static Error Error.NotFound(string code, string description);
public static Error Error.Failure(string code, string description, Dictionary<string, object>? metadata = null);
public static Error Error.Unexpected(string code, string description, Dictionary<string, object>? metadata = null);
public static Error Error.Validation(string code, string description, Dictionary<string, object>? metadata = null)
public static Error Error.Unexpected(string code, string description, Dictionary<string, object>? metadata = null);
public static Error Error.Conflict(string code, string description, Dictionary<string, object>? metadata = null);
public static Error Error.NotFound(string code, string description, Dictionary<string, object>? metadata = null);
public static Error Error.Unauthorized(string code, string description, Dictionary<string, object>? metadata = null);
```

The `ErrorType` enum is a good way to categorize errors.
Expand Down
50 changes: 25 additions & 25 deletions src/Error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,81 +26,81 @@ public readonly record struct Error
public int NumericType { get; }

/// <summary>
/// Gets the dictionary.
/// Gets the metadata.
/// </summary>
public Dictionary<string, object>? Dictionary { get; }
public Dictionary<string, object>? Metadata { get; }

/// <summary>
/// Creates an <see cref="Error"/> of type <see cref="ErrorType.Failure"/> from a code and description.
/// </summary>
/// <param name="code">The unique error code.</param>
/// <param name="description">The error description.</param>
/// <param name="dictionary">A dictionary which provides optional space for information.</param>
/// <param name="metadata">A dictionary which provides optional space for information.</param>
public static Error Failure(
string code = "General.Failure",
string description = "A failure has occurred.",
Dictionary<string, object>? dictionary = null) =>
new(code, description, ErrorType.Failure, dictionary);
Dictionary<string, object>? metadata = null) =>
new(code, description, ErrorType.Failure, metadata);

/// <summary>
/// Creates an <see cref="Error"/> of type <see cref="ErrorType.Unexpected"/> from a code and description.
/// </summary>
/// <param name="code">The unique error code.</param>
/// <param name="description">The error description.</param>
/// <param name="dictionary">A dictionary which provides optional space for information.</param>
/// <param name="metadata">A dictionary which provides optional space for information.</param>
public static Error Unexpected(
string code = "General.Unexpected",
string description = "An unexpected error has occurred.",
Dictionary<string, object>? dictionary = null) =>
new(code, description, ErrorType.Unexpected, dictionary);
Dictionary<string, object>? metadata = null) =>
new(code, description, ErrorType.Unexpected, metadata);

/// <summary>
/// Creates an <see cref="Error"/> of type <see cref="ErrorType.Validation"/> from a code and description.
/// </summary>
/// <param name="code">The unique error code.</param>
/// <param name="description">The error description.</param>
/// <param name="dictionary">A dictionary which provides optional space for information.</param>
/// <param name="metadata">A dictionary which provides optional space for information.</param>
public static Error Validation(
string code = "General.Validation",
string description = "A validation error has occurred.",
Dictionary<string, object>? dictionary = null) =>
new(code, description, ErrorType.Validation, dictionary);
Dictionary<string, object>? metadata = null) =>
new(code, description, ErrorType.Validation, metadata);

/// <summary>
/// Creates an <see cref="Error"/> of type <see cref="ErrorType.Conflict"/> from a code and description.
/// </summary>
/// <param name="code">The unique error code.</param>
/// <param name="description">The error description.</param>
/// <param name="dictionary">A dictionary which provides optional space for information.</param>
/// <param name="metadata">A dictionary which provides optional space for information.</param>
public static Error Conflict(
string code = "General.Conflict",
string description = "A conflict error has occurred.",
Dictionary<string, object>? dictionary = null) =>
new(code, description, ErrorType.Conflict, dictionary);
Dictionary<string, object>? metadata = null) =>
new(code, description, ErrorType.Conflict, metadata);

/// <summary>
/// Creates an <see cref="Error"/> of type <see cref="ErrorType.NotFound"/> from a code and description.
/// </summary>
/// <param name="code">The unique error code.</param>
/// <param name="description">The error description.</param>
/// <param name="dictionary">A dictionary which provides optional space for information.</param>
/// <param name="metadata">A dictionary which provides optional space for information.</param>
public static Error NotFound(
string code = "General.NotFound",
string description = "A 'Not Found' error has occurred.",
Dictionary<string, object>? dictionary = null) =>
new(code, description, ErrorType.NotFound, dictionary);
Dictionary<string, object>? metadata = null) =>
new(code, description, ErrorType.NotFound, metadata);

/// <summary>
/// Creates an <see cref="Error"/> of type <see cref="ErrorType.Unauthorized"/> from a code and description.
/// </summary>
/// <param name="code">The unique error code.</param>
/// <param name="description">The error description.</param>
/// <param name="dictionary">A dictionary which provides optional space for information.</param>
/// <param name="metadata">A dictionary which provides optional space for information.</param>
public static Error Unauthorized(
string code = "General.Unauthorized",
string description = "An 'Unauthorized' error has occurred.",
Dictionary<string, object>? dictionary = null) =>
new(code, description, ErrorType.Unauthorized, dictionary);
Dictionary<string, object>? metadata = null) =>
new(code, description, ErrorType.Unauthorized, metadata);

/// <summary>
/// Creates an <see cref="Error"/> with the given numeric <paramref name="type"/>,
Expand All @@ -109,20 +109,20 @@ public static Error Unauthorized(
/// <param name="type">An integer value which represents the type of error that occurred.</param>
/// <param name="code">The unique error code.</param>
/// <param name="description">The error description.</param>
/// <param name="dictionary">A dictionary which provides optional space for information.</param>
/// <param name="metadata">A dictionary which provides optional space for information.</param>
public static Error Custom(
int type,
string code,
string description,
Dictionary<string, object>? dictionary = null) =>
new(code, description, (ErrorType)type, dictionary);
Dictionary<string, object>? metadata = null) =>
new(code, description, (ErrorType)type, metadata);

private Error(string code, string description, ErrorType type, Dictionary<string, object>? dictionary)
private Error(string code, string description, ErrorType type, Dictionary<string, object>? metadata)
{
Code = code;
Description = description;
Type = type;
NumericType = (int)type;
Dictionary = dictionary;
Metadata = metadata;
}
}
2 changes: 1 addition & 1 deletion tests/ErrorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,6 @@ private static void ValidateError(Error error, ErrorType expectedErrorType)
error.Description.Should().Be(ErrorDescription);
error.Type.Should().Be(expectedErrorType);
error.NumericType.Should().Be((int)expectedErrorType);
error.Dictionary.Should().BeEquivalentTo(Dictionary);
error.Metadata.Should().BeEquivalentTo(Dictionary);
}
}

0 comments on commit 6485ae8

Please sign in to comment.