-
Notifications
You must be signed in to change notification settings - Fork 0
Results
This package provides a simple and flexible implementation of the Result Pattern. The Result Pattern is a way of representing the outcome of an operation, whether it's successful or has encountered an error, in a more explicit and structured manner.
The package is lightweight, extensible, immutable and thread-safe.
The package consists of only three classes Result, Result<TValue>, and Error (with associated interfaces)
- The
Resultclass represents a generic result indicating success or failure. - The
Result<TValue>class represents a success or failure result with a value. - The
Errorclass represents an error with a message and optional associated metadata.
Successful results can be created using the Success method.
var successResult = Result.Success();
var successResultWithValue = Result.Success(349.4);Failed results can be created using the Failure method.
var failureResult = Result.Failure();
var failureResultWithMessage = Result.Failure("Operation failure!");
var failureResultWithMessageAndMetadata = Result.Failure("Operation failure!", ("UserId", userId));
var failureResultWithMessageAndException = Result.Failure("Operation failure!", ex);There are two methods used to check a result, IsSuccess() and IsFailed(). Both of which have several overloads to obtain the
value and error.
if (result.IsSuccess())
{
// The result is successful.
}
if (result.IsFailure(out var error))
{
// The result is failure.
if (error.Message.Length > 0)
Console.WriteLine(error.Message);
else
Console.WriteLine("An unknown error occured!");
}The value from a successful result can be retrieved through the out parameter of the Success() method.
if (result.IsSuccess(out var value))
{
Console.WriteLine($"Value is {value}");
}Errors can be created with or without a message.
var errorWithoutMessage = new Error();
var errorWithMessage = new Error("Something went wrong!");Or with a message and metadata.
var errorWithMetadataTuple = new Error("Something went wrong!", ("Key", "Value"));
var metadata = new Dictionary<string, object> { { "Key", "Value" } };
var errorWithMetadataDictionary = new Error("Something went wrong!", metadata);The best way to represent specific errors is to make custom error classes that inherit from Error
and define the error message as a base constructor parameter.
public sealed class NotFoundError : Error
{
public NotFoundError()
: base("The resource cannot be found.")
{
}
}
var notFoundError = new NotFoundError();
var notFoundResult = Result.Failure(notFoundError);Then the result can be checked against that error type.
if (result.IsFailure(out var error) && error is NotFoundError)
{
// Looks like the resource was not found, we better do something about that!
}Or checked to see if there are any errors of that type.
if (result.IsFailure() && result.HasError<NotFoundError>())
{
// At least one of the errors was a NotFoundError.
}This can be especially useful when combined with metadata that is related to a specific type of error.
public sealed class HttpError : Error
{
public HttpError(HttpStatusCode statusCode)
: base("An HTTP error occured.", ("StatusCode", statusCode))
{
}
}We can further simplify creating errors by creating an error factory.
public static AppError
{
public Result NotFound()
{
var notFoundError = new NotFoundError();
return Result.Failure(notFoundError);
}
public Result HttpError(HttpStatusCode statusCode)
{
var httpError = new HttpError(statusCode)
return Result.Failure(httpError);
}
}Which clearly and explicitly describes the results.
public Result GetPerson(int id)
{
var person = _database.GetPerson(id);
if (person is null)
return AppError.NotFound();
return Result.Success();
}Specific overloads have been added to Failure() and Failure<TValue>() to simplify using try-catch blocks and return from them with a result instead of
throwing.
public Result DoSomeWork()
{
try
{
// We must not throw an exception in this method!
}
catch(Exception ex)
{
return Result.Failure(ex);
}
return Result.Success();
}The package provides the ErrorFactory class to simplify creation of common error types:
IError CreateError(HttpStatusCode statusCode) will create the following:
| HttpStatusCode | Error Type |
|---|---|
| BadRequest | BadRequestError |
| Unauthorized | UnauthorizedError |
| ServiceUnavailable | ServiceUnavailableError |
| BadGateway | DownstreamServiceError |
| NotFound | NotFoundError |
| InternalServerError | InternalServerError |
| (other)) | HttpError |
This is based on the LightResults package, found here, with some modifications and additions.