Skip to content

Instantly share code, notes, and snippets.

@vkhorikov
Last active July 30, 2024 14:25
Show Gist options
  • Save vkhorikov/7852c7606f27c52bc288 to your computer and use it in GitHub Desktop.
Save vkhorikov/7852c7606f27c52bc288 to your computer and use it in GitHub Desktop.
Handling failures and input errors in a functional way
[HttpPost]
public HttpResponseMessage CreateCustomer(string name, string billingInfo)
{
Result<BillingInfo> billingInfoResult = BillingInfo.Create(billingInfo);
Result<CustomerName> customerNameResult = CustomerName.Create(name);
return Result.Combine(billingInfoResult, customerNameResult)
.OnSuccess(() => _paymentGateway.ChargeCommission(billingInfoResult.Value))
.OnSuccess(() => new Customer(customerNameResult.Value))
.OnSuccess(
customer => _repository.Save(customer)
.OnFailure(() => _paymentGateway.RollbackLastTransaction())
)
.OnSuccess(() => _emailSender.SendGreetings(customerNameResult.Value))
.OnBoth(result => Log(result))
.OnBoth(result => CreateResponseMessage(result));
}
public class Result
{
public bool Success { get; private set; }
public string Error { get; private set; }
public bool Failure
{
get { return !Success; }
}
protected Result(bool success, string error)
{
Contracts.Require(success || !string.IsNullOrEmpty(error));
Contracts.Require(!success || string.IsNullOrEmpty(error));
Success = success;
Error = error;
}
public static Result Fail(string message)
{
return new Result(false, message);
}
public static Result<T> Fail<T>(string message)
{
return new Result<T>(default(T), false, message);
}
public static Result Ok()
{
return new Result(true, String.Empty);
}
public static Result<T> Ok<T>(T value)
{
return new Result<T>(value, true, String.Empty);
}
public static Result Combine(params Result[] results)
{
foreach (Result result in results)
{
if (result.Failure)
return result;
}
return Ok();
}
}
public class Result<T> : Result
{
private T _value;
public T Value
{
get
{
Contracts.Require(Success);
return _value;
}
[param: AllowNull]
private set { _value = value; }
}
protected internal Result([AllowNull] T value, bool success, string error)
: base(success, error)
{
Contracts.Require(value != null || !success);
Value = value;
}
}
public static class ResultExtensions
{
public static Result OnSuccess(this Result result, Func<Result> func)
{
if (result.Failure)
return result;
return func();
}
public static Result OnSuccess(this Result result, Action action)
{
if (result.Failure)
return result;
action();
return Result.Ok();
}
public static Result OnSuccess<T>(this Result<T> result, Action<T> action)
{
if (result.Failure)
return result;
action(result.Value);
return Result.Ok();
}
public static Result<T> OnSuccess<T>(this Result result, Func<T> func)
{
if (result.Failure)
return Result.Fail<T>(result.Error);
return Result.Ok(func());
}
public static Result<T> OnSuccess<T>(this Result result, Func<Result<T>> func)
{
if (result.Failure)
return Result.Fail<T>(result.Error);
return func();
}
public static Result OnSuccess<T>(this Result<T> result, Func<T, Result> func)
{
if (result.Failure)
return result;
return func(result.Value);
}
public static Result OnFailure(this Result result, Action action)
{
if (result.Failure)
{
action();
}
return result;
}
public static Result OnBoth(this Result result, Action<Result> action)
{
action(result);
return result;
}
public static T OnBoth<T>(this Result result, Func<Result, T> func)
{
return func(result)
}
}
@trongphan
Copy link

The Contracts class where you put all your common validate there?

@Brezelmann
Copy link

Brezelmann commented Oct 28, 2018

You are missing a semicolon on line 74 in ResultExtensions.

@ShaharShokrani
Copy link

ShaharShokrani commented Jun 1, 2019

Hey @vkhorikov thank you! I'm using .Net core and the [param: AllowNull] doesn't compile , I'm getting

the type or namespace name 'AllowNullAttribute' could not be found...

Any suggestion?

@vkhorikov
Copy link
Author

@ShaharShokrani you need to install NullGuard.Fody for that. It's optional, though, this would work without it. More on the null guard:

https://www.nuget.org/packages/NullGuard.Fody/
https://enterprisecraftsmanship.com/2015/03/13/functional-c-non-nullable-reference-types/

@dehghani-mehdi
Copy link

What is Contracts?

@vkhorikov
Copy link
Author

vkhorikov commented Oct 12, 2019

It's a simple helper, e.g

public static class Guard
{
    public static void Require(bool precondition, string exceptionMessage)
    {
        if (!precondition)
            throw new CodeContractException(exceptionMessage);
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment