ThomasW.Domain.SharedKernel.Results 0.2.0

dotnet add package ThomasW.Domain.SharedKernel.Results --version 0.2.0
NuGet\Install-Package ThomasW.Domain.SharedKernel.Results -Version 0.2.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ThomasW.Domain.SharedKernel.Results" Version="0.2.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ThomasW.Domain.SharedKernel.Results --version 0.2.0
#r "nuget: ThomasW.Domain.SharedKernel.Results, 0.2.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install ThomasW.Domain.SharedKernel.Results as a Cake Addin
#addin nuget:?package=ThomasW.Domain.SharedKernel.Results&version=0.2.0

// Install ThomasW.Domain.SharedKernel.Results as a Cake Tool
#tool nuget:?package=ThomasW.Domain.SharedKernel.Results&version=0.2.0

ThomasW.Domain.SharedKernel.Results

Indicate the success of domain or application operations without throwing slow and expensive exceptions.

Installation

This package can be installed via NuGet.

Usage

Creation of results is achieved by accessing the static methods on the Result class.

For operations that do not return a value, such as application commands, you can create basic result objects using the Fail() and Success() methods:

public async Task<Result> CreateUser(Guid userId, User user)
{
    if (userId == default)
    {
        return Result.Fail(new InvalidEntityIdFailure());
    }

    await this._repository.AddUser(userId, user);

    return Result.Success();
}

In the above example, an argument is passed into the Fail() method that indicates the failure reason. This type must inherit from the FailureReason abstract class.

For operations that do return a value, such as application queries, you can create typed result objects using the Fail<T>() and Success<T>() methods:

public async Task<Result<User>> GetUser(Guid userId)
{
    User? user = await this._repository.GetUser(userId);

    if (user == null)
    {
        return Result.Fail<User>(new EntityNotFoundFailure());
    }

    return Result.Success(user);
}

In the preceding example, a type is passed into the Fail<T>() method to indicate the type of value that would have been returned had the operation been successful.

As the value is passed directly into the Success<T>() method, its type can be inferred and thus does not have to be explicitly specified.

For instances in which there may be multiple possible failure reasons, specifying the value type in each call to Fail<T>() will soon become laborious. To make this easier, you can create pending typed results with the Pending<T>() method and specify the failure reason when necessary:

public async Task<Result<User>> GetUser(Guid userId)
{
    PendingResult<User> result = Result.Pending<User>();

    if (userId == default)
    {
        return result.Fail(new InvalidEntityIdFailure());
    }

    User? user = await this._repository.GetUser(userId);

    if (user == null)
    {
        return result.Fail(new EntityNotFoundFailure());
    }

    return result.Success(user);
}

When consuming results, you can determine their success with the IsSuccessful and IsFailed properties, which will indicate whether the Value and FailureReason properties are null:

public async IActionResult Get(Guid userId)
{
    Result<User> result = await this._userService.Get(userId);

    if (result.IsSuccessful)
    {
        return this.Ok(result.Value);
    }

    if (result.FailureReason is EntityNotFoundFailure)
    {
        return this.NotFound();
    }

    return this.BadRequest();
}

Here, we check that the result is successful, which will tell the compiler that the Value property is not null, and the FailureReason property is null.

Conversely, if we checked that the operation failed, this will tell the compiler that the FailureReason property is not null, and the Value property is null:

public async IActionResult Get(Guid userId)
{
    Result<User> result = await this._userService.Get(userId);

    if (result.IsFailed)
    {
        return result.FailureReason switch
        {
            EntityNotFoundFailure => this.NotFound(),
            _ => this.BadRequest()
        };
    }

    return this.Ok(result.Value);
}

ThomasW.Domain.SharedKernel.Results.FluentAssertions

To make testing result objects easier, a set of FluentAssertions extensions are available to install via NuGet.

The package currently provides two assertion methods, BeSuccessful and BeFailed, for both typed and non-typed results.

Simple checks on the success of a result can be made by using the methods with no arguments:

public async Task GetUser_ValidId_ReturnsUser()
{
    // Arrange Act
    Result<User> result = await this._sut.GetUser(this._userId);

    // Assert
    result.Should().BeSuccessful();
}

public async Task GetUser_InvalidId_ReturnsUser()
{
    // Arrange Act
    Result<User> result = await this._sut.GetUser(Guid.NewGuid());

    // Assert
    result.Should().BeFailed();
}

If you want to assert on the value of a successful result, you can pass a predicate into the method. The assertion will only pass if the result is successful and the predicate evaluates to true:

public async Task GetUser_ValidId_ReturnsUser()
{
    // Arrange Act
    Result<User> result = await this._sut.GetUser(this._userId);

    // Assert
    result.Should().BeSuccessful(user => user.Id == this._userId);
}

You can also assert on the failure reason by passing a type argument into the IsFailed method:

public async Task GetUser_InvalidId_ReturnsUser()
{
    // Arrange Act
    Result<User> result = await this._sut.GetUser(Guid.NewGuid());

    // Assert
    result.Should().BeFailed<EntityNotFoundFailure>();
}

This assertion will only pass if the result is failed and the failure reason matches the passed-in type.

Furthermore, you can pass a predicate into the IsFailed method, as well as the type parameter, to assert against the FailureReason object itself:

public async Task GetUser_InvalidId_ReturnsUser()
{
    // Arrange Act
    Result<User> result = await this._sut.GetUser(Guid.NewGuid());

    // Assert
    result.Should().BeFailed<EntityNotFoundFailure>(
        reason => reason.Message == "User not found");
}

The above assertion will only pass if the result is failed, the failure reason matches the passed-in type and the predicate evaluates to true.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on ThomasW.Domain.SharedKernel.Results:

Package Downloads
ThomasW.Domain.SharedKernel.Results.FluentAssertions

FluentAssertions extensions for ThomasW.Domain.SharedKernel.Results

ThomasW.Application.Commands.Abstractions

A set of abstractions for building an application command pipeline.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.2.0 360 12/2/2021
0.2.0-beta.0.1 132 12/2/2021
0.2.0-alpha.0.2 126 12/2/2021
0.2.0-alpha.0.1 127 12/2/2021
0.1.1 268 12/1/2021
0.1.1-alpha.0.1 135 12/1/2021
0.1.0 521 12/1/2021
0.0.0-alpha.0.14 128 12/1/2021
0.0.0-alpha.0.13 130 12/1/2021