Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// The NUnit and Chessie Nuget packages are required for this
// To install NUnit and Chessie run the following commands from the Package Manager Console
// PM> Install-Package NUnit -Version 2.6.4
// PM> Install-Package Chessie
using System;
using System.Text.RegularExpressions;
using Chessie.ErrorHandling;
using Chessie.ErrorHandling.CSharp;
using Microsoft.FSharp.Core;
using NUnit.Framework;
namespace ErrorHandlingSamples.CSharp
{
public class Customer
{
public int Id { get; private set; }
public string Name { get; private set; }
public string Email { get; private set; }
private Customer() { }
internal static Customer Create(int id, string name, string email) { return new Customer{ Id = id, Name = name, Email = email }; }
public static Result<Customer, string> CreateResult(int id, string name, string email)
{
var idResult = ValidateId(id);
var nameResult = ValidateName(name);
var emailResult = ValidateEmail(email);
return
new Func<int, string, string, Customer>(Create)
.Curry()
.Map(idResult)
.Apply(nameResult)
.Apply(emailResult);
}
// some dummy checks
private static Result<string, string> ValidateEmail(string email)
{
var regex = new Regex(@"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$");
return !String.IsNullOrWhiteSpace(email) && regex.IsMatch(email)
? Result<string, string>.Succeed(email)
: Result<string, string>.FailWith("email not valid");
}
private static Result<string, string> ValidateName(string name)
{
return name.Length > 1
? Result<string, string>.Succeed(name)
: Result<string, string>.FailWith("name not valid");
}
private static Result<int, string> ValidateId(int id)
{
return id > 0
? Result<int, string>.Succeed(id)
: Result<int, string>.FailWith("id not valid");
}
}
public static class Extensions
{
public static Result<T2, TMessage> Apply<T1, T2, TMessage>(this Result<Func<T1, T2>, TMessage> wrappedFunc, Result<T1, TMessage> result)
{
var convertedFunc = wrappedFunc.Select(f => FSharpFunc<T1, T2>.FromConverter(x => f(x)));
return Trial.apply(convertedFunc, result);
}
public static Result<T2, TMessage> Map<T1, T2, TMessage>(this Func<T1, T2> f, Result<T1, TMessage> result)
{
return result.Select(f);
}
public static Func<T1, Func<T2, Func<T3, T4>>> Curry<T1, T2, T3, T4>(this Func<T1, T2, T3, T4> f)
{
return x1 => x2 => x3 => f(x1, x2, x3);
}
}
[TestFixture]
public class ErrorHandlingSamplesCSharpTests
{
[Test]
public void Select_Test()
{
var rOk = Result<int, string>.Succeed(16);
var rBad = Result<int, string>.FailWith("error");
rOk
.Select(x => x * 2)
.Match(
ifSuccess: (v,_) => Assert.That(v, Is.EqualTo(32)),
ifFailure: _ => Assert.Fail("Should be ok, but was bad."));
rBad
.Select(x => x * 2)
.Match(
ifSuccess: (v, _) => Assert.Fail("Should be bad, but was ok."),
ifFailure: errs => Assert.That(errs, Is.EquivalentTo(new[]{"error"})));
}
[Test]
public void SelectMany_Test()
{
var validate = new Func<Customer, Result<Customer, string>>(Result<Customer, string>.Succeed);
var update = new Func<Customer, Result<Customer, string>>(c => Result<Customer, string>.FailWith("update error"));
var send = new Func<Customer, Result<Customer, string>>(c => Result<Customer, string>.FailWith("send error"));
var customer = Customer.Create(42, "John", "email@example.com");
// with LINQ query syntax
var resultLinq =
from v in validate(customer)
from u in update(v)
from s in send(u)
select s;
resultLinq.Match(
ifSuccess: (v, _) => Assert.Fail("Should be bad, but was ok."),
ifFailure: errs => Assert.That(errs, Is.EquivalentTo(new[] { "update error" })));
// with extensions methods
var resultExt = validate(customer)
.SelectMany(update)
.SelectMany(send);
resultExt.Match(
ifSuccess: (v,_) => Assert.Fail("Should be bad, but was ok."),
ifFailure: errs => Assert.That(errs, Is.EquivalentTo(new[] {"update error"})));
}
[Test]
public void Apply_Test()
{
var idResult = Result<int, string>.FailWith("id not valid");
var nameResult = Result<string, string>.Succeed("John");
var emailResult = Result<string, string>.FailWith("email not valid");
// with LINQ query syntax
var customerLinq =
from id in idResult
join name in nameResult on 1 equals 1
join email in emailResult on 1 equals 1
select Customer.Create(id, name, email);
customerLinq.Match(
ifSuccess: (v, _) => Assert.Fail("Should be bad, but was ok."),
ifFailure: errs => Assert.That(errs, Is.EquivalentTo(new[] { "id not valid", "email not valid" })));
// with extension methods
var customerExt = idResult
.Select(new Func<int, Func<string, Func<string, Customer>>>(id => name => email => Customer.Create(id, name, email)))
.Apply(nameResult)
.Apply(emailResult);
customerExt.Match(
ifSuccess: (v, _) => Assert.Fail("Should be bad, but was ok."),
ifFailure: errs => Assert.That(errs, Is.EquivalentTo(new[] { "id not valid", "email not valid" })));
// with Map extension method
var create = new Func<int, string, string, Customer>(Customer.Create).Curry();
var customerMap = create
.Map(idResult)
.Apply(nameResult)
.Apply(emailResult);
customerMap.Match(
ifSuccess: (v, _) => Assert.Fail("Should be bad, but was ok."),
ifFailure: errs => Assert.That(errs, Is.EquivalentTo(new[] { "id not valid", "email not valid" })));
}
[Test]
public void Bypass_Test()
{
var idResult = Result<int, string>.FailWith("id not valid");
var nameResult = Result<string, string>.Succeed("John");
var emailResult = Result<string, string>.FailWith("email not valid");
var result =
from id in idResult
from name in nameResult
from email in emailResult
select Customer.Create(id, name, email);
result.Match(
ifSuccess: (v, _) => Assert.Fail("Should be bad, but was ok."),
ifFailure: errs => Assert.That(errs, Is.EquivalentTo(new[] { "id not valid" })));
}
}
}
// The Chessie and NUnit Nuget packages are required for this
// To install Chessie and NUnit run the following commands from the Package Manager Console
// PM> Install-Package Chessie
// PM> Install-Package NUnit -Version 2.6.4
module ErrorHandlingSamples.FSharp
open Chessie.ErrorHandling
open System.Text.RegularExpressions
type Customer = { id:int; name:string; email:string }
// some dummy checks
let validateId id = if id > 0 then ok id else fail "id not valid"
let validateName (name: string) = if name.Length > 1 then ok name else fail "name not valid"
let validateEmail (email: string) =
let regex = new Regex(@"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
match regex.IsMatch(email) with
| true -> ok email
| false -> fail "email not valid"
let Create id name email =
let idResult = validateId id
let nameResult = validateName name
let emailResult = validateEmail email
let create = fun id name email -> { id = id; name = name; email = email }
create
<!> idResult
<*> nameResult
<*> emailResult
let Create' id (name:string) (email:string) =
let idResult = validateId id
let nameResult = validateName name
let emailResult = validateEmail email
let create = fun id name email -> { id = id; name = name; email = email }
idResult
>>= fun id -> nameResult
>>= fun name -> emailResult
>>= fun email -> ok (create id name email)
module Tests =
open Chessie.ErrorHandling
open NUnit.Framework
// curried version of AreEqual
let shouldEqual (x : 'a) (y : 'a) = Assert.AreEqual(x, y, sprintf "Expected: %A\nActual: %A" x y)
[<Test>]
let ``lift should multiply inner value of success by 2``() =
ok 16
|> Trial.lift ((*) 2)
|> shouldEqual (ok 32)
[<Test>]
let ``lift applied to a failure should fail``() =
fail "error"
|> Trial.lift ((*) 2)
|> shouldEqual (Bad [ "error" ])
[<Test>]
let ``<!> should multiply inner value of success by 2``() =
((*) 2)
<!> ok 16
|> shouldEqual (ok 32)
[<Test>]
let ``<!> applied to a failure should fail``() =
((*) 2)
<!> fail "error"
|> shouldEqual (Bad [ "error" ])
[<Test>]
let ``bind should bypass after first error``() =
let validate c = ok c
let update c = fail "update error"
let send c = fail "send error"
let customer = { id = 42; name = "John"; email = "john@example.com" }
validate customer
>>= update
>>= send
|> shouldEqual (Bad [ "update error" ])
[<Test>]
let ``bind should create valid customer if no failures``() =
let validate c = ok c
let update c = ok c
let send c = ok c
let customer = { id = 42; name = "John"; email = "john@example.com" }
validate customer
>>= update
>>= send
|> shouldEqual (ok { id = 42; name = "John"; email = "john@example.com" })
[<Test>]
let ``apply valid inputs should create valid customer``() =
Create 42 "John" "john@example.com"
|> shouldEqual (ok { id = 42; name = "John"; email = "john@example.com" })
[<Test>]
let ``apply invalid inputs should fail with accumulated messages``() =
Create -1 "John" "foo"
|> shouldEqual (Bad [ "id not valid"; "email not valid" ])
[<Test>]
let ``bind invalid inputs should fail with first messages``() =
Create' -1 "John" "foo"
|> shouldEqual (Bad [ "id not valid" ])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.