Skip to content

Commit

Permalink
Merge branch 'master' into add-validator
Browse files Browse the repository at this point in the history
  • Loading branch information
inputfalken committed Sep 23, 2018
2 parents 7eef8ac + 63001ac commit f6cde89
Show file tree
Hide file tree
Showing 61 changed files with 762 additions and 1,200 deletions.
4 changes: 1 addition & 3 deletions samples/ConsoleInputValidation/Program.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
using System;
using System.Linq;
using Lemonad.ErrorHandling;
using Lemonad.ErrorHandling.Extensions;

namespace ConsoleInputValidation {
internal static class Program {
private static int Main(string[] args) {
Console.WriteLine("Please supply your name.");
return Console.ReadLine()
.ToResult<string, ExitCode>()
.IsErrorWhen(x => string.IsNullOrWhiteSpace(x), () => ExitCode.EmptyName)
.ToResult(s => string.IsNullOrEmpty(s) == false, () => ExitCode.EmptyName)
.Flatten(OnlyAlphanumericLetters)
.Map(_ => ExitCode.Success)
.FullCast<int>()
Expand Down
15 changes: 8 additions & 7 deletions samples/MvcValidation/Controller/AsyncPersonController.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
using Lemonad.ErrorHandling;
using Lemonad.ErrorHandling.Extensions;
using Microsoft.AspNetCore.Mvc;
using MvcValidation.ApiModels;
using MvcValidation.Models;
Expand All @@ -10,7 +9,7 @@ namespace MvcValidation.Controller {
public class AsyncPersonController : Microsoft.AspNetCore.Mvc.Controller {
private static Result<PersonPostApiModel, PersonPostApiError> ApiValidation(PersonPostApiModel model) {
var apiValidation = model
.ToResult<PersonPostApiModel, PersonPostApiError>()
.ToResult(x => true, () => default(PersonPostApiError))
.Multiple(
x => x.Filter(y => y.Age > 10,
() => new PersonPostApiError {Message = "Age needs to be more than 10", Model = model}),
Expand Down Expand Up @@ -46,18 +45,20 @@ private static async Task<Result<SuccessModel, ErrorModel>> LastNameAppService(P
[HttpPost]
[Route("eitherSummarized")]
public Task<IActionResult> PostPerson([FromBody] PersonPostApiModel model) {
var lastNameAppService = LastNameAppService(new PersonModel()).AsOutcome();
return ApiValidation(model)
// Using match inside this scope is currently too complex since it requires all type params to be supplied.
.Map(x => new PersonModel {FirstName = x.FirstName, LastName = x.LastName})
.Flatten(LastNameAppService, x => new PersonPostApiError {Message = x.Message, Model = model})
.FlatMap(FirstNameAppService, x => new PersonPostApiError {Message = x.Message, Model = model})
.ToAsyncResult()
.Flatten(x => LastNameAppService(x).ToAsyncResult(),
x => new PersonPostApiError {Message = x.Message, Model = model})
.FlatMap(x => FirstNameAppService(x).ToAsyncResult(),
x => new PersonPostApiError {Message = x.Message, Model = model})
.Match<IActionResult>(Ok, BadRequest);
}

private static Result<string, string> ValidateName(string name) {
return name.ToResult<string, string>()
.IsErrorWhen(x => string.IsNullOrWhiteSpace(x), () => "Name cannot be empty.")
return name
.ToResult(x => string.IsNullOrWhiteSpace(x) == false, () => "Name cannot be empty.")
.Filter(s => s.All(char.IsLetter), () => "Name can only contain letters.")
.Filter(s => char.IsUpper(s[0]), () => "Name must start with capital letter.");
}
Expand Down
7 changes: 3 additions & 4 deletions samples/MvcValidation/Controller/PersonController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Linq;
using Lemonad.ErrorHandling;
using Lemonad.ErrorHandling.Extensions;
using Microsoft.AspNetCore.Mvc;
using MvcValidation.ApiModels;
using MvcValidation.Models;
Expand All @@ -10,7 +9,7 @@ namespace MvcValidation.Controller {
public class PersonController : Microsoft.AspNetCore.Mvc.Controller {
private static Result<PersonPostApiModel, PersonPostApiError> ApiValidation(PersonPostApiModel model) {
var apiValidation = model
.ToResult<PersonPostApiModel, PersonPostApiError>()
.ToResult(x => true, () => default(PersonPostApiError))
.Multiple(
x => x.Filter(y => y.Age > 10,
() => new PersonPostApiError {Message = "Age needs to be more than 10", Model = model}),
Expand Down Expand Up @@ -50,8 +49,8 @@ public IActionResult PostPerson([FromBody] PersonPostApiModel model) {
}

private static Result<string, string> ValidateName(string name) {
return name.ToResult<string, string>()
.IsErrorWhen(x => string.IsNullOrWhiteSpace(x), () => "Name cannot be empty.")
return name
.ToResult(x => string.IsNullOrWhiteSpace(x) == false, () => "Name cannot be empty.")
.Filter(s => s.All(char.IsLetter), () => "Name can only contain letters.")
.Filter(s => char.IsUpper(s[0]), () => "Name must start with capital letter.");
}
Expand Down
3 changes: 1 addition & 2 deletions samples/ReadFileAsync/ExitCode.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
namespace ReadFileAsync
{
namespace ReadFileAsync {
internal static partial class Program {
internal enum ExitCode {
FileNotFound = 1,
Expand Down
12 changes: 6 additions & 6 deletions samples/ReadFileAsync/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@
using System.IO;
using System.Threading.Tasks;
using Lemonad.ErrorHandling;
using Lemonad.ErrorHandling.Extensions;

namespace ReadFileAsync {
internal static partial class Program {
private static void LogFatal(string message, Exception exception) {
// Log fatal somewhere...
}

private static async Task<int> Main(string[] args) {
var result = await "data.txt"
.ToResult(File.Exists, () => ExitCode.FileNotFound)
.Filter(x => Path.GetExtension(x) == ".txt", () => ExitCode.InvalidFileExtension)
.ToAsyncResult()
.Map(s => File.ReadAllTextAsync(s))
.Filter(s => s == "Hello World", () => ExitCode.InvalidFileContent)
.FlatMap(s => ProcessText(s, "processed.txt"))
.FlatMap(s => ProcessText(s, "processed.txt").ToAsyncResult())
.Match(x => (ExitCode: 0, Message: x),
x => {
string message;
Expand Down Expand Up @@ -41,10 +45,6 @@ private static async Task<int> Main(string[] args) {
return result.ExitCode;
}

private static void LogFatal(string message, Exception exception) {
// Log fatal somewhere...
}

private static async Task<Result<string, ExitCode>> ProcessText(
string text, string filePath) {
// You can also handle exceptions more effectivly with Result<T, TError>.
Expand Down
188 changes: 188 additions & 0 deletions src/Lemonad.ErrorHandling/AsyncResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Threading.Tasks;
using Lemonad.ErrorHandling.Internal;

namespace Lemonad.ErrorHandling {
/// <summary>
/// An asynchronous version of <see cref="Result{T,TError}" /> with the same functionality.
/// </summary>
public readonly struct AsyncResult<T, TError> {
private AsyncResult(Task<Result<T, TError>> result) =>
TaskResult = result ?? throw new ArgumentNullException(nameof(result));

internal Task<Result<T, TError>> TaskResult { get; }

public static implicit operator AsyncResult<T, TError>(Task<Result<T, TError>> result) =>
new AsyncResult<T, TError>(result);

public static implicit operator AsyncResult<T, TError>(T value) =>
new AsyncResult<T, TError>(Task.FromResult(ResultExtensions.Value<T, TError>(value)));

public AsyncResult<TResult, TError> Join<TInner, TKey, TResult>(
AsyncResult<TInner, TError> inner, Func<T, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector,
Func<T, TInner, TResult> resultSelector, Func<TError> errorSelector, IEqualityComparer<TKey> comparer) =>
TaskResultFunctions.Join(TaskResult, inner.TaskResult, outerKeySelector, innerKeySelector, resultSelector,
errorSelector, comparer);

public AsyncResult<TResult, TError> Join<TInner, TKey, TResult>(
AsyncResult<TInner, TError> inner, Func<T, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector,
Func<T, TInner, TResult> resultSelector, Func<TError> errorSelector) =>
TaskResultFunctions.Join(TaskResult, inner.TaskResult, outerKeySelector, innerKeySelector, resultSelector,
errorSelector);

[Pure]
public AsyncResult<TResult, TError> Zip<TOther, TResult>(AsyncResult<TOther, TError> other,
Func<T, TOther, TResult> resultSelector) =>
TaskResultFunctions.Zip(TaskResult, other.TaskResult, resultSelector);

private static async Task<Result<T, TError>> Factory(Task<T> foo) => await foo.ConfigureAwait(false);
private static async Task<Result<T, TError>> ErrorFactory(Task<TError> foo) => await foo.ConfigureAwait(false);

public static implicit operator AsyncResult<T, TError>(Task<T> value) => Factory(value);

public static implicit operator AsyncResult<T, TError>(Task<TError> error) => ErrorFactory(error);

public static implicit operator AsyncResult<T, TError>(TError error) =>
new AsyncResult<T, TError>(Task.FromResult(ResultExtensions.Error<T, TError>(error)));

/// <inheritdoc cref="Result{T,TError}.Filter(System.Func{T,bool},System.Func{TError})" />
public AsyncResult<T, TError> Filter(Func<T, bool> predicate, Func<TError> errorSelector) =>
TaskResultFunctions.Filter(TaskResult, predicate, errorSelector);

/// <inheritdoc cref="Result{T,TError}.Filter(System.Func{T,bool},System.Func{Maybe{T},TError})" />
public AsyncResult<T, TError> Filter(Func<T, bool> predicate, Func<Maybe<T>, TError> errorSelector) =>
TaskResultFunctions.Filter(TaskResult, predicate, errorSelector);

public AsyncResult<T, TError> Filter(Func<T, Task<bool>> predicate, Func<TError> errorSelector) =>
TaskResultFunctions.Filter(TaskResult, predicate, errorSelector);

/// <inheritdoc cref="Result{T,TError}.Filter(System.Func{T,bool},System.Func{Maybe{T},TError})" />
public AsyncResult<T, TError> Filter(Func<T, Task<bool>> predicate, Func<Maybe<T>, TError> errorSelector) =>
TaskResultFunctions.Filter(TaskResult, predicate, errorSelector);

/// <inheritdoc cref="Result{T,TError}.HasError" />
public Task<bool> HasError => TaskResultFunctions.HasError(TaskResult);

/// <inheritdoc cref="Result{T,TError}.HasValue" />
public Task<bool> HasValue => TaskResultFunctions.HasValue(TaskResult);

/// <inheritdoc cref="Result{T,TError}.Multiple" />
public AsyncResult<T, IReadOnlyList<TError>> Multiple(
params Func<Result<T, TError>, Result<T, TError>>[] validations) =>
TaskResultFunctions.Multiple(TaskResult, validations);

/// <inheritdoc cref="Result{T,TError}.IsErrorWhen(Func{T,bool},Func{TError})" />
public AsyncResult<T, TError> IsErrorWhen(
Func<T, bool> predicate,
Func<TError> errorSelector) =>
TaskResultFunctions.IsErrorWhen(TaskResult, predicate, errorSelector);

public AsyncResult<T, TError> IsErrorWhen(
Func<T, bool> predicate,
Func<Maybe<T>, TError> errorSelector) =>
TaskResultFunctions.IsErrorWhen(TaskResult, predicate, errorSelector);

public AsyncResult<T, TError> IsErrorWhen(
Func<T, Task<bool>> predicate,
Func<TError> errorSelector) => TaskResultFunctions.IsErrorWhen(TaskResult, predicate, errorSelector);

public AsyncResult<T, TError> IsErrorWhen(
Func<T, Task<bool>> predicate,
Func<Maybe<T>, TError> errorSelector) =>
TaskResultFunctions.IsErrorWhen(TaskResult, predicate, errorSelector);

/// <inheritdoc cref="Result{T,TError}.IsErrorWhenNull(System.Func{TError})" />
public AsyncResult<T, TError> IsErrorWhenNull(Func<TError> errorSelector) =>
TaskResultFunctions.IsErrorWhenNull(TaskResult, errorSelector);

public AsyncResult<TResult, TError> Map<TResult>(Func<T, TResult> selector) =>
TaskResultFunctions.Map(TaskResult, selector);

public AsyncResult<TResult, TError> Map<TResult>(Func<T, Task<TResult>> selector) =>
TaskResultFunctions.Map(TaskResult, selector);

public AsyncResult<T, TErrorResult> MapError<TErrorResult>(Func<TError, TErrorResult> selector) =>
TaskResultFunctions.MapError(TaskResult, selector);

public AsyncResult<T, TErrorResult> MapError<TErrorResult>(Func<TError, Task<TErrorResult>> selector) =>
TaskResultFunctions.MapError(TaskResult, selector);

/// <inheritdoc cref="Result{T,TError}.Do" />
public AsyncResult<T, TError> Do(Action action) => TaskResultFunctions.Do(TaskResult, action);

/// <inheritdoc cref="Result{T,TError}.DoWithError" />
public AsyncResult<T, TError> DoWithError(Action<TError> action) =>
TaskResultFunctions.DoWithError(TaskResult, action);

/// <inheritdoc cref="Result{T,TError}.DoWith" />
public AsyncResult<T, TError> DoWith(Action<T> action) => TaskResultFunctions.DoWith(TaskResult, action);

/// <inheritdoc cref="Result{T,TError}.FullMap{TResult,TErrorResult}" />
public AsyncResult<TResult, TErrorResult> FullMap<TResult, TErrorResult>(
Func<T, TResult> selector,
Func<TError, TErrorResult> errorSelector
) => TaskResultFunctions.FullMap(TaskResult, selector, errorSelector);

/// <inheritdoc cref="Result{T,TError}.Match{TResult}" />
public Task<TResult> Match<TResult>(Func<T, TResult> selector, Func<TError, TResult> errorSelector) =>
TaskResultFunctions.Match(TaskResult, selector, errorSelector);

/// <inheritdoc cref="Result{T,TError}.Match" />
public Task Match(Action<T> action, Action<TError> errorAction) =>
TaskResultFunctions.Match(TaskResult, action, errorAction);

public AsyncResult<TResult, TError> FlatMap<TResult>(Func<T, AsyncResult<TResult, TError>> flatSelector) =>
TaskResultFunctions.FlatMap(TaskResult, x => flatSelector(x).TaskResult);

public AsyncResult<TResult, TError> FlatMap<TSelector, TResult>(
Func<T, AsyncResult<TSelector, TError>> flatSelector,
Func<T, TSelector, TResult> resultSelector) =>
TaskResultFunctions.FlatMap(TaskResult, x => flatSelector(x).TaskResult, resultSelector);

public AsyncResult<TResult, TError> FlatMap<TResult, TErrorResult>(
Func<T, AsyncResult<TResult, TErrorResult>> flatMapSelector, Func<TErrorResult, TError> errorSelector) =>
TaskResultFunctions.FlatMap(TaskResult, x => flatMapSelector(x).TaskResult, errorSelector);

public AsyncResult<TResult, TError> FlatMap<TFlatMap, TResult, TErrorResult>(
Func<T, AsyncResult<TFlatMap, TErrorResult>> flatMapSelector, Func<T, TFlatMap, TResult> resultSelector,
Func<TErrorResult, TError> errorSelector) =>
TaskResultFunctions.FlatMap(TaskResult, x => flatMapSelector(x).TaskResult, resultSelector, errorSelector);

/// <inheritdoc cref="Result{T,TError}.Cast{TResult}" />
public AsyncResult<TResult, TError> Cast<TResult>() => TaskResultFunctions.Cast<T, TResult, TError>(TaskResult);

public AsyncResult<T, TError> Flatten<TResult, TErrorResult>(
Func<T, AsyncResult<TResult, TErrorResult>> selector, Func<TErrorResult, TError> errorSelector) =>
TaskResultFunctions.Flatten(TaskResult, x => selector(x).TaskResult, errorSelector);

public AsyncResult<T, TError> Flatten<TResult>(Func<T, AsyncResult<TResult, TError>> selector) =>
TaskResultFunctions.Flatten(TaskResult, x => selector(x).TaskResult);

/// <inheritdoc cref="Result{T,TError}.FullCast{TResult,TErrorResult}" />
public AsyncResult<TResult, TErrorResult> FullCast<TResult, TErrorResult>() =>
TaskResultFunctions.FullCast<T, TResult, TError, TErrorResult>(TaskResult);

/// <inheritdoc cref="Result{T,TError}.FullCast{TResult}" />
public AsyncResult<TResult, TResult> FullCast<TResult>() => FullCast<TResult, TResult>();

/// <inheritdoc cref="Result{T,TError}.CastError{TResult}" />
public AsyncResult<T, TResult> CastError<TResult>() =>
TaskResultFunctions.CastError<T, TError, TResult>(TaskResult);

/// <inheritdoc cref="Result{T,TError}.SafeCast{TResult}" />
public AsyncResult<TResult, TError> SafeCast<TResult>(Func<TError> errorSelector) =>
TaskResultFunctions.SafeCast<T, TResult, TError>(TaskResult, errorSelector);

public AsyncResult<TResult, TErrorResult> FullFlatMap<TFlatMap, TResult, TErrorResult>(
Func<T, AsyncResult<TFlatMap, TErrorResult>> flatMapSelector, Func<T, TFlatMap, TResult> resultSelector,
Func<TError, TErrorResult> errorSelector) =>
TaskResultFunctions.FullFlatMap(TaskResult, x => flatMapSelector(x).TaskResult, resultSelector,
errorSelector);

public AsyncResult<TResult, TErrorResult> FullFlatMap<TResult, TErrorResult>(
Func<T, AsyncResult<TResult, TErrorResult>> flatMapSelector, Func<TError, TErrorResult> errorSelector) =>
TaskResultFunctions.FullFlatMap(TaskResult, x => flatMapSelector(x).TaskResult, errorSelector);
}
}
Loading

0 comments on commit f6cde89

Please sign in to comment.