Giter Site home page Giter Site logo

fluentresults's Introduction

FluentResults

FluentResults

Nuget downloads Nuget Build status License: MIT

FluentResults is a lightweight .NET library built to solve a common problem - returning an object indicates success or failure of an operation instead of using exceptions.

You should install FluentResults with NuGet:

Install-Package FluentResults

Key Features

  • Storing multiple errors in one Result object
  • Storing powerful Error and Success objects and not only error string messages
  • Designing Errors/Success in an object-oriented way
  • Storing the root cause chain of errors in a hierarchical way
  • Provide
    • .NET Standard, .NET Core and .NET Full Framework support
    • SourceLink support
    • powerful code samples which show the integration with famous or common frameworks/libraries

Why Results instead of exceptions

To be honest, returning a Result object indicates success or failure is not a new idea. Martin Fowler already wrote about this pattern in 2004.

If you want to know what Martin Fowler think about Results read that: Notification by Martin Fowler

If you want a second opinion read that: Error handling: Exception or Result? by Vladimir Khorikov

If you want a short summary read that: Error Handling — Returning Results by Michael Altmann

Creating a Result

A Result can store multiple Error and Success messages.

// create a result which indicates success
Result successResult1 = Result.Ok();
			  
// create a result which indicates failure
Result errorResult1 = Result.Fail("My error message");
Result errorResult2 = Result.Fail(new StartDateIsAfterEndDateError(startDate, endDate));

The class Result is typically used by void methods which have no return value.

public Result DoTask()
{
    if (this.State == TaskState.Done)
        return Result.Fail("Task is in the wrong state.");

    // rest of the logic

    return Result.Ok();
}

Additionally a value from a specific type can also be stored if necessary.

// create a result which indicates success
Result<int> successResult1 = Result.Ok(42);
Result<MyCustomObject> successResult2 = Result.Ok(new MyCustomObject());

// create a result which indicates failure
Result<int> errorResult = Result.Fail<int>("My error message");

The class Result<T> is typically used by methods with a return type.

public Result<Task> GetTask()
{
    if (this.State == TaskState.Deleted)
        return Result.Fail<Task>("Deleted Tasks can not be displayed.");

    // rest of the logic

    return Result.Ok(task);
}

Processing a Result

After you get a Result object from a method you have to process it. This means, you have to check if the operation completed successfully or not. The properties IsSuccess and IsFailed at the Result object indicates success or failure. The value of a Result<T> can be accessed via the properties Value and ValueOrDefault.

 Result<int> result = DoSomething();
 
 // get all reasons why result object indicates success or failure. 
 // contains Error and Success messages
 IEnumerable<Reason> reasons = result.Reasons;
 
 // get all Error messages
 IEnumerable<Error> errors = result.Errors;
 
 // get all Success messages
 IEnumerable<Success> successes = result.Successes;

 if (result.IsFailed)
 {
      // handle error case
      var value1 = result.Value; // throws exception because result is in failed state
      var value2 = result.ValueOrDefault; // return default value (=0) because result is in failed state
      return;
 }

 // handle success case
 var value3 = result.Value; // return value and doesn't throw exception because result is in success state
 var value4 = result.ValueOrDefault; // return value because result is in success state

Designing errors and success messages

There are many Result Libraries which stores only simple string messages. FluentResults stores instead powerful object-oriented Error and Success objects. The advantage is that all relevant information of an error is encapsulated within one class.

The classes Success and Error inherit from the base class Reason. If at least one Error object is in the Reasons property then the result indicates a failure and the property IsSuccess is false.

You can create your own Success or Error classes when you inherit from Success or Error.

public class StartDateIsAfterEndDateError : Error
{
    public StartDateIsAfterEndDateError(DateTime startDate, DateTime endDate)
        : base($"The start date {startDate} is after the end date {endDate}")
    { 
        Metadata.Add("ErrorCode", "12");
    }
}

With this mechanism you can also create a class Warning. You can choose if a Warning in your system indicates a success or a failure by inheriting from Success or Error classes.

Further features

Chaining error and success messages

In some cases it is necessary to chain multiple error and success messages in one result object.

var result = Result.Fail("error message 1")
                   .WithError("error message 2")
                   .WithError("error message 3")
                   .WithSuccess("success message 1");

Root cause of the error

You can also store the root cause of the error in the error object.

try
{
    //export csv file
}
catch(CsvExportException ex)
{
    return Result.Fail(new Error("CSV Export not executed successfully")
                       .CausedBy(ex));
}

Metadata

It is possible to add metadata to Error or Success objects.

One way doing that is to call the method WithMetadata(...) directly at the creation of the result object.

var result1 = Result.Fail(new Error("Error 1")
                          .WithMetadata("metadata name", "metadata value"));

var result2 = Result.Ok()
                    .WithSuccess(new Success("Success 1")
                                 .WithMetadata("metadata name", "metadata value"));

Another way is to call WithMetadata(...) in constructor of the Error or Success class.

public class DomainError : Error
{
    public DomainError(string message)
        : base(message)
    { 
        WithMetadata("ErrorCode", "12");
    }
}

Merging

Multiple results can be merged with the static method Merge().

var result1 = Result.Ok();
var result2 = Result.Fail("first error");
var result3 = Result.Ok<int>();

var mergedResult = Result.Merge(result1, result2, result3);

Converting

A result object can be converted to another result object with the methods ToResult() and ToResult<TValue>().

Result.Ok().ToResult<int>(); // converting a result to a result from type Result<int>
Result.Ok<int>(5).ToResult<float>(v => v); // converting a result to a result from type Result<float>
Result.Fail<int>("Failed").ToResult<float>() // converting a result from type Result<int> to result from type Result<float> without passing the converting logic because result is in failed state and therefore no converting logic needed
Result.Ok<int>().ToResult(); // converting a result to a result from type Result

Handling/catching errors

Similar to the catch block for exceptions the checking and handling of errors within a Result object is supported by this library with some methods:

result.HasError<MyCustomError>(); // check if the Result object contains an error from a specific type
result.HasError<MyCustomError>(myCustomError => myCustomError.MyField == 2); // check if the Result object contains an error from a specific type and with a specific condition
result.HasError(error => error.HasMetadataKey("MyKey")); // check if the Result object contains an error with a specific metadata key
result.HasError(error => error.HasMetadata("MyKey", metadataValue => (string)metadataValue == "MyValue")); // check if the Result object contains an error with a specific metadata

Handling successes

Checking if a result object contains a specific success object can be done with the method HasSuccess()

result.HasSuccess<MyCustomSuccess>(); // check if the Result object contains a success from a specific type
result.HasSuccess<MyCustomSuccess>(success => success.MyField == 3); // check if the Result object contains a success from a specific type and with a specific condition

Logging

Sometimes it is necessary to log results. First create a logger.

public class MyConsoleLogger : IResultLogger
{
    public void Log(string context, ResultBase result)
    {
        Console.WriteLine("{0}", result);
    }
}

Then you have to register your logger.

var myLogger = new MyConsoleLogger();
        Result.Setup(cfg => {
            cfg.Logger = myLogger;
        });

Finally the logger can be used.

var result = Result.Fail("Operation failed")
    .Log();

Additionally a context as string can be passed.

var result = Result.Fail("Operation failed")
    .Log("logger context");

Samples/Best Practices

Here are some samples and best practices using FluentResult or the Result pattern in general with some famous or common used frameworks and libraries.

MediatR request handlers returning Result objects

Copyright

Copyright (c) Michael Altmann. See LICENSE for details.

fluentresults's People

Contributors

altmann avatar maltmann avatar michaelmcneilnet avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.