Skip to content

Commit

Permalink
minor: add result type
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasteles committed May 23, 2023
1 parent 84b59ff commit 54c7723
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 67 deletions.
4 changes: 2 additions & 2 deletions src/EnumerablePlus/LinqEnumerablePlus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ public static void ForEach<T>(this IEnumerable<T> source, Action<T, int> action)
var rnd = random ?? Random.Shared;
return source switch
{
IReadOnlyCollection<T> {Count: 0} => default,
IReadOnlyList<T> {Count: > 0} list => list[rnd.Next(list.Count)],
IReadOnlyCollection<T> { Count: 0 } => default,
IReadOnlyList<T> { Count: > 0 } list => list[rnd.Next(list.Count)],
_ => source.Shuffle(rnd).FirstOrDefault(defaultValue),
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/Result/Json/ResultJsonTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class ResultJsonConverterFactory : JsonConverterFactory

/// <inheritdoc />
public class ResultJsonConverter<TOk, TError> : JsonConverter<Result<TOk, TError>>
where TError : notnull

{
readonly JsonConverter<TOk> okConverter;
readonly JsonConverter<TError> errorConverter;
Expand Down
58 changes: 3 additions & 55 deletions src/Result/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using CSharpPlus.Result.Json;

namespace CSharpPlus.Result;
Expand All @@ -15,7 +14,7 @@ namespace CSharpPlus.Result;
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
[System.Text.Json.Serialization.JsonConverter(typeof(ResultJsonConverterFactory))]
public readonly struct Result<TOk, TError> : IEnumerable<TOk>, IEquatable<Result<TOk, TError>>
where TError : notnull

{
internal TOk? OkValue { get; }
internal TError? ErrorValue { get; }
Expand Down Expand Up @@ -152,24 +151,6 @@ IEnumerator<TOk> IEnumerable<TOk>.GetEnumerator()
public T Match<T>(Func<TOk, T> ok, Func<TError, T> error) =>
IsOk ? ok(this.OkValue) : error(this.ErrorValue);

/// <summary>
/// Match the result to obtain the value
/// </summary>
public async Task<T> Match<T>(Func<TOk, Task<T>> ok, Func<TError, Task<T>> error) =>
IsOk ? await ok(this.OkValue) : await error(this.ErrorValue);

/// <summary>
/// Match the result to obtain the value
/// </summary>
public async Task<T> Match<T>(Func<TOk, Task<T>> ok, Func<TError, T> error) =>
IsOk ? await ok(this.OkValue) : error(this.ErrorValue);

/// <summary>
/// Match the result to obtain the value
/// </summary>
public async Task<T> Match<T>(Func<TOk, T> ok, Func<TError, Task<T>> error) =>
IsOk ? ok(this.OkValue) : await error(this.ErrorValue);

/// <summary>
/// Switch the result to process value
/// </summary>
Expand All @@ -181,39 +162,6 @@ public void Switch(Action<TOk> ok, Action<TError> error)
error(this.ErrorValue);
}

/// <summary>
/// Switch the result to process value
/// </summary>
public async Task Switch(Func<TOk, Task> ok, Func<TError, Task> error)
{
if (IsOk)
await ok(this.OkValue);
else
await error(this.ErrorValue);
}

/// <summary>
/// Switch the result to process value
/// </summary>
public async Task Switch(Func<TOk, Task> ok, Action<TError> error)
{
if (IsOk)
await ok(this.OkValue);
else
error(this.ErrorValue);
}

/// <summary>
/// Switch the result to process value
/// </summary>
public async Task Switch(Action<TOk> ok, Func<TError, Task> error)
{
if (IsOk)
ok(this.OkValue);
else
await error(this.ErrorValue);
}

/// <summary>
/// Attempts to extract value from container if it is present.
/// </summary>
Expand All @@ -239,15 +187,15 @@ public bool TryGet([NotNullWhen(true)] out TOk? value)
public Result<TMapOk, TMapError> Select<TMapOk, TMapError>(
Func<TOk, TMapOk> okSelector,
Func<TError, TMapError> errorSelector
) where TMapError : notnull => Match(
) => Match(
ok => new Result<TMapOk, TMapError>(okSelector(ok)),
error => new(errorSelector(error))
);

/// <summary>
/// Projects error result element into a new form.
/// </summary>
public Result<TOk, TMap> SelectError<TMap>(Func<TError, TMap> selector) where TMap : notnull =>
public Result<TOk, TMap> SelectError<TMap>(Func<TError, TMap> selector) =>
Match(
ok => new Result<TOk, TMap>(ok),
error => new(selector(error))
Expand Down
176 changes: 168 additions & 8 deletions src/Result/ResultExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
Expand All @@ -12,7 +13,7 @@ public static class Result
/// <summary>
/// Represents an OK or a Successful result. The code succeeded with a value of 'T
/// </summary>
public static Result<TOk, TError> Ok<TOk, TError>(TOk result) where TError : notnull =>
public static Result<TOk, TError> Ok<TOk, TError>(TOk result) =>
Result<TOk, TError>.Ok(result);

/// <summary>
Expand All @@ -24,7 +25,7 @@ public static class Result
/// <summary>
/// Represents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
/// </summary>
public static Result<TOk, TError> Error<TOk, TError>(TError error) where TError : notnull =>
public static Result<TOk, TError> Error<TOk, TError>(TError error) =>
Result<TOk, TError>.Error(error);

/// <summary>
Expand Down Expand Up @@ -75,17 +76,38 @@ public static class Result
/// Convert result of task into task of result
/// </summary>
public static async Task<Result<TOk, TError>> ToTask<TOk, TError>(
this Result<Task<TOk>, TError> result) where TError : notnull
this Result<Task<TOk>, TError> result)
{
if (result.IsOk) return await result.OkValue;
return result.ErrorValue;
}

/// <summary>
/// Convert result of task into task of result
/// </summary>
public static async Task<Result<TOk, TError>> ToTask<TOk, TError>(
this Result<TOk, Task<TError>> result)
{
if (result.IsOk) return result.OkValue;
return await result.ErrorValue;
}

/// <summary>
/// Convert result of task into task of result
/// </summary>
public static async Task<Result<TOk, TError>> ToTask<TOk, TError>(
this Result<Task<TOk>, Task<TError>> result)
{
if (result.IsOk) return await result.OkValue;
return await result.ErrorValue;
}


/// <summary>
/// Convert result of task into task of result
/// </summary>
public static Result<IReadOnlyList<TOk>, TError> ToResult<TOk, TError>(
this IEnumerable<Result<TOk, TError>> results) where TError : notnull
this IEnumerable<Result<TOk, TError>> results)
{
List<TOk> okResults = new();
foreach (var result in results)
Expand All @@ -103,14 +125,14 @@ public static class Result
/// Return new collection with ok values only
/// </summary>
public static IEnumerable<TOk> Choose<TOk, TError>(
this IEnumerable<Result<TOk, TError>> results) where TError : notnull =>
this IEnumerable<Result<TOk, TError>> results) =>
from result in results where result.IsOk select result.OkValue;

/// <summary>
/// Return new collection with ok values only
/// </summary>
public static IEnumerable<TError> ChooseErrors<TOk, TError>(
this IEnumerable<Result<TOk, TError>> results) where TError : notnull =>
this IEnumerable<Result<TOk, TError>> results) =>
from result in results where result.IsError select result.ErrorValue;

/// <summary>
Expand All @@ -119,6 +141,144 @@ public static class Result
/// <returns>Nullable value.</returns>
public static T? ToNullable<T, TError>(this Result<T, TError> valueResult)
where T : struct
where TError : notnull =>
valueResult.IsOk ? valueResult.OkValue : null;
=>
valueResult.IsOk ? valueResult.OkValue : null;

/// <summary>
/// Switch the result to process value
/// </summary>
public static async Task Switch<TOk, TError>(this Result<TOk, TError> result,
Func<TOk, Task> ok,
Func<TError, Task> error)
{
if (result.IsOk)
await ok(result.OkValue);
else
await error(result.ErrorValue);
}

/// <summary>
/// Switch the result to process value
/// </summary>
public static async Task Switch<TOk, TError>(this Result<TOk, TError> result,
Func<TOk, Task> ok, Action<TError> error)
{
if (result.IsOk)
await ok(result.OkValue);
else
error(result.ErrorValue);
}

/// <summary>
/// Switch the result to process value
/// </summary>
public static async Task Switch<TOk, TError>(this Result<TOk, TError> result,
Action<TOk> ok,
Func<TError, Task> error)
{
if (result.IsOk)
ok(result.OkValue);
else
await error(result.ErrorValue);
}

/// <summary>
/// Match the result to obtain the value
/// </summary>
public static async Task<T> Match<TOk, TError, T>(this Result<TOk, TError> result,
Func<TOk, Task<T>> ok, Func<TError, Task<T>> error) =>
result.IsOk ? await ok(result.OkValue) : await error(result.ErrorValue);

/// <summary>
/// Match the result to obtain the value
/// </summary>
public static async Task<T> Match<TOk, TError, T>(this Result<TOk, TError> result,
Func<TOk, Task<T>> ok,
Func<TError, T> error) =>
result.IsOk ? await ok(result.OkValue) : error(result.ErrorValue);

/// <summary>
/// Match the result to obtain the value
/// </summary>
public static async Task<T>
Match<TOk, TError, T>(this Result<TOk, TError> result,
Func<TOk, T> ok,
Func<TError, Task<T>> error) =>
result.IsOk ? ok(result.OkValue) : await error(result.ErrorValue);

/// <summary>
/// Projects ok result value into a new form.
/// </summary>e
public static Task<Result<TMap, TError>> SelectAsync<TOk, TError, TMap>(
this Result<TOk, TError> result,
Func<TOk, Task<TMap>> selector
) => result.Select(selector).ToTask();

/// <summary>
/// Projects ok result value into a new form.
/// </summary>e
public static Task<Result<TMapOk, TMapError>> SelectAsync<TOk, TError, TMapOk, TMapError>(
this Result<TOk, TError> result,
Func<TOk, Task<TMapOk>> okSelector,
Func<TError, Task<TMapError>> errorSelector
) => result.Select(okSelector, errorSelector).ToTask();

/// <summary>
/// Projects ok result value into a new form.
/// </summary>e
public static Task<Result<TMapOk, TMapError>> SelectAsync<TOk, TError, TMapOk, TMapError>(
this Result<TOk, TError> result,
Func<TOk, TMapOk> okSelector,
Func<TError, Task<TMapError>> errorSelector
) => result.Select(okSelector, errorSelector).ToTask();

/// <summary>
/// Projects ok result value into a new form.
/// </summary>e
public static Task<Result<TMapOk, TMapError>> SelectAsync<TOk, TError, TMapOk, TMapError>(
this Result<TOk, TError> result,
Func<TOk, Task<TMapOk>> okSelector,
Func<TError, TMapError> errorSelector
) => result.Select(okSelector, errorSelector).ToTask();

/// <summary>
/// Projects ok result value into a new form.
/// </summary>e
public static Task<Result<TOk, TMap>> SelectErrorAsync<TOk, TError, TMap>(
this Result<TOk, TError> result,
Func<TError, Task<TMap>> selector
) => result.SelectError(selector).ToTask();


/// <summary>
/// Projects ok result value into a new flattened form.
/// </summary>
public static Task<Result<TMap, TError>> SelectManyAsync<TOk, TError, TMap>(
this Result<TOk, TError> result,
Func<TOk, Result<Task<TMap>, TError>> bind) =>
result.SelectMany(bind).ToTask();

/// <summary>
/// Projects ok result value into a new flattened form.
/// </summary>
public static Task<Result<TMap, TError>> SelectManyAsync<TOk, TError, TMap>(
this Result<TOk, TError> result,
Func<TOk, Result<TMap, Task<TError>>> bind) =>
result.SelectError(Task.FromResult).SelectMany(bind).ToTask();

/// <summary>
/// Projects ok result value into a new flattened form.
/// </summary>
public static Task<Result<TMap, TError>> SelectManyAsync<TOk, TError, TMap>(
this Result<TOk, TError> result,
Func<TOk, Result<Task<TMap>, Task<TError>>> bind) =>
result.SelectError(Task.FromResult).SelectMany(bind).ToTask();

/// <summary>
/// Projects ok result value into a new flattened form.
/// </summary>
public static async Task<Result<TMap, TError>> SelectManyAsync<TOk, TError, TMap>(
this Result<TOk, TError> result,
Func<TOk, Task<Result<TMap, TError>>> bind) =>
result.IsError ? new Result<TMap, TError>(result.ErrorValue) : await bind(result.OkValue);
}
18 changes: 17 additions & 1 deletion tests/CSharpPlus.Tests/ResultTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public void ShouldBeEnumerable()
[Test]
public void ShouldMatchPropertyOk()
{
if (Result<int, string>.Ok(42) is {Value: 42})
if (Result<int, string>.Ok(42) is { Value: 42 })
Assert.Pass();

Assert.Fail("unexpected!");
Expand Down Expand Up @@ -106,4 +106,20 @@ public void ShouldTryGet()

Assert.Fail();
}

[Test]
public async Task ShouldMapAsync()
{
var result = await Result<int, string>.Ok(42).SelectAsync(Task.FromResult);
result.Should().Equal(Result.Ok(42));
}

[Test]
public async Task ShouldBindAsync()
{
var result = await Result<int, string>.Ok(42)
.SelectManyAsync(x => Task.FromResult(Result.Ok(x + 10)));

result.Should().Equal(Result.Ok(52));
}
}

0 comments on commit 54c7723

Please sign in to comment.