Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Bind in addition to Map to support Railway Oriented Programming #215

Merged
merged 2 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
357 changes: 324 additions & 33 deletions src/Ardalis.Result/ResultExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,47 +1,338 @@
using System;
using System.Linq;
using System.Threading.Tasks;

namespace Ardalis.Result
{
public static class ResultExtensions
{
/// <summary>
/// Transforms a Result's type from a source type to a destination type. If the Result is successful, the func parameter is invoked on the Result's source value to map it to a destination type.
/// Transforms a Result's type from a source type to a destination type.
/// If the Result is successful, the func parameter is invoked on the Result's source value to map it to a destination type.
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TDestination"></typeparam>
/// <param name="result"></param>
/// <param name="func"></param>
/// <returns></returns>
/// <exception cref="NotSupportedException"></exception>
/// <typeparam name="TSource">The type of the value contained in the source Result.</typeparam>
/// <typeparam name="TDestination">The type of the value to be returned in the destination Result.</typeparam>
/// <param name="result">The source Result to transform.</param>
/// <param name="func">A function to transform the source value to the destination type.</param>
/// <returns>A Result containing the transformed value or the appropriate error status.</returns>
/// <exception cref="NotSupportedException">Thrown when the Result status is not supported.</exception>
public static Result<TDestination> Map<TSource, TDestination>(this Result<TSource> result, Func<TSource, TDestination> func)
{
switch (result.Status)
{
case ResultStatus.Ok: return func(result);
case ResultStatus.Created: return string.IsNullOrEmpty(result.Location)
? Result<TDestination>.Created(func(result.Value))
: Result<TDestination>.Created(func(result.Value), result.Location);
case ResultStatus.NotFound: return result.Errors.Any()
? Result<TDestination>.NotFound(result.Errors.ToArray())
: Result<TDestination>.NotFound();
case ResultStatus.Unauthorized: return result.Errors.Any()
? Result<TDestination>.Unauthorized(result.Errors.ToArray())
: Result<TDestination>.Unauthorized();
case ResultStatus.Forbidden: return result.Errors.Any()
? Result<TDestination>.Forbidden(result.Errors.ToArray())
: Result<TDestination>.Forbidden();
case ResultStatus.Invalid: return Result<TDestination>.Invalid(result.ValidationErrors);
case ResultStatus.Error: return Result<TDestination>.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId));
case ResultStatus.Conflict: return result.Errors.Any()
? Result<TDestination>.Conflict(result.Errors.ToArray())
: Result<TDestination>.Conflict();
case ResultStatus.CriticalError: return Result<TDestination>.CriticalError(result.Errors.ToArray());
case ResultStatus.Unavailable: return Result<TDestination>.Unavailable(result.Errors.ToArray());
case ResultStatus.NoContent: return Result<TDestination>.NoContent();
default:
throw new NotSupportedException($"Result {result.Status} conversion is not supported.");
}
return result.Status switch
{
ResultStatus.Ok => (Result<TDestination>)func(result),
ResultStatus.Created => string.IsNullOrEmpty(result.Location)
? Result<TDestination>.Created(func(result.Value))
: Result<TDestination>.Created(func(result.Value), result.Location),
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
};
}

public static Result<TDestination> Map<TDestination>(this Result result, Func<TDestination> func)
{
return result.Status switch
{
ResultStatus.Ok => Result<TDestination>.Success(func()),
ResultStatus.Created => string.IsNullOrEmpty(result.Location)
? Result<TDestination>.Created(func())
: Result<TDestination>.Created(func(), result.Location),
_ => HandleNonSuccessStatus<TDestination>(result),
};
}

public static async Task<Result<TDestination>> MapAsync<TSource, TDestination>(
this Result<TSource> result,
Func<TSource, Task<TDestination>> func)
{
return result.Status switch
{
ResultStatus.Ok => Result<TDestination>.Success(await func(result.Value)),
ResultStatus.Created => string.IsNullOrEmpty(result.Location)
? Result<TDestination>.Created(await func(result.Value))
: Result<TDestination>.Created(await func(result.Value), result.Location),
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
};
}


public static async Task<Result<TDestination>> MapAsync<TSource, TDestination>(
this Task<Result<TSource>> resultTask,
Func<TSource, Task<TDestination>> func)
{
var result = await resultTask;
return result.Status switch
{
ResultStatus.Ok => Result<TDestination>.Success(await func(result.Value)),
ResultStatus.Created => string.IsNullOrEmpty(result.Location)
? Result<TDestination>.Created(await func(result.Value))
: Result<TDestination>.Created(await func(result.Value), result.Location),
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
};
}

public static async Task<Result<TDestination>> MapAsync<TDestination>(
this Result result,
Func<Task<TDestination>> func)
{
return result.Status switch
{
ResultStatus.Ok => Result<TDestination>.Success(await func()),
ResultStatus.Created => string.IsNullOrEmpty(result.Location)
? Result<TDestination>.Created(await func())
: Result<TDestination>.Created(await func(), result.Location),
_ => HandleNonSuccessStatus<TDestination>(result),
};
}

public static async Task<Result<TDestination>> MapAsync<TDestination>(
this Task<Result> resultTask,
Func<Task<TDestination>> func)
{
var result = await resultTask;
return await result.MapAsync(func);
}


public static async Task<Result<TDestination>> MapAsync<TSource, TDestination>(
this Task<Result<TSource>> resultTask,
Func<TSource, TDestination> func)
{
var result = await resultTask;
return result.Map(func);
}

public static async Task<Result<TDestination>> MapAsync<TDestination>(
this Task<Result> resultTask,
Func<TDestination> func)
{
var result = await resultTask;
return result.Map(func);
}

/// <summary>
/// Transforms a Result's type from a source type to a destination type.
/// If the Result is successful, the func parameter is invoked on the Result's source value to map it to a destination type.
/// </summary>
/// <typeparam name="TSource">The type of the value contained in the source Result.</typeparam>
/// <typeparam name="TDestination">The type of the value to be returned in the destination Result.</typeparam>
/// <param name="result">The source Result to transform.</param>
/// <param name="func">A function to transform the source value to the destination type.</param>
/// <returns>A Result containing the transformed value or the appropriate error status.</returns>
/// <exception cref="NotSupportedException">Thrown when the Result status is not supported.</exception>
public static Result<TDestination> Bind<TSource, TDestination>(this Result<TSource> result, Func<TSource, Result<TDestination>> bindFunc)
{
return result.Status switch
{
ResultStatus.Ok => bindFunc(result.Value),
ResultStatus.Created => bindFunc(result.Value),
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
};
}

public static Result<TDestination> Bind<TDestination>(this Result result, Func<Result, Result<TDestination>> bindFunc)
{
return result.Status switch
{
ResultStatus.Ok => bindFunc(result.Value),
ResultStatus.Created => bindFunc(result.Value),
_ => HandleNonSuccessStatus<TDestination>(result),
};
}

public static Result Bind<TSource>(this Result<TSource> result, Func<Result<TSource>, Result> bindFunc)
{
return result.Status switch
{
ResultStatus.Ok => bindFunc(result.Value),
ResultStatus.Created => bindFunc(result.Value),
_ => HandleNonSuccessStatus(result),
};
}

public static async Task<Result<TDestination>> BindAsync<TSource, TDestination>(
this Task<Result<TSource>> resultTask,
Func<TSource, Task<Result<TDestination>>> bindFunc)
{
var result = await resultTask;
return result.Status switch
{
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
};
}

public static async Task<Result> BindAsync<TSource>(
this Task<Result<TSource>> resultTask,
Func<TSource, Task<Result>> bindFunc)
{
var result = await resultTask;
return result.Status switch
{
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
_ => HandleNonSuccessStatus(result),
};
}

public static async Task<Result> BindAsync<TSource>(
this Result<TSource> result,
Func<TSource, Task<Result>> bindFunc)
{
return result.Status switch
{
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
_ => HandleNonSuccessStatus(result),
};
}

public static async Task<Result> BindAsync(
this Result result,
Func<Result, Task<Result>> bindFunc)
{
return result.Status switch
{
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
_ => HandleNonSuccessStatus(result),
};
}

public static async Task<Result<TDestination>> BindAsync<TDestination>(
this Task<Result> resultTask,
Func<Result, Task<Result<TDestination>>> bindFunc)
{
var result = await resultTask;
return result.Status switch
{
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
_ => HandleNonSuccessStatus<TDestination>(result),
};
}

public static async Task<Result<TDestination>> BindAsync<TSource, TDestination>(
this Result<TSource> result,
Func<TSource, Task<Result<TDestination>>> bindFunc)
{
return result.Status switch
{
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
};
}

public static async Task<Result> BindAsync<TSource, TDestination>(
this Result<TSource> result,
Func<TSource, Task<Result>> bindFunc)
{
return result.Status switch
{
ResultStatus.Ok => await bindFunc(result.Value).ConfigureAwait(false),
ResultStatus.Created => await bindFunc(result.Value).ConfigureAwait(false),
_ => HandleNonSuccessStatus(result),
};
}

public static async Task<Result> BindAsync(this Task<Result> resultTask, Func<Result, Task<Result>> bindFunc)
{
var result = await resultTask;
return result.Status switch
{
ResultStatus.Ok => await bindFunc(result).ConfigureAwait(false),
ResultStatus.Created => await bindFunc(result).ConfigureAwait(false),
_ => HandleNonSuccessStatus(result),
};
}

public static async Task<Result<TDestination>> BindAsync<TSource, TDestination>(
this Task<Result<TSource>> resultTask,
Func<TSource, Result<TDestination>> bindFunc)
{
var result = await resultTask;
return result.Status switch
{
ResultStatus.Ok => bindFunc(result.Value),
ResultStatus.Created => bindFunc(result.Value),
_ => HandleNonSuccessStatus<TSource, TDestination>(result),
};
}

private static Result<TDestination> HandleNonSuccessStatus<TSource, TDestination>(Result<TSource> result)
{
return result.Status switch
{
ResultStatus.NotFound => result.Errors.Any()
? Result<TDestination>.NotFound(result.Errors.ToArray())
: Result<TDestination>.NotFound(),
ResultStatus.Unauthorized => result.Errors.Any()
? Result<TDestination>.Unauthorized(result.Errors.ToArray())
: Result<TDestination>.Unauthorized(),
ResultStatus.Forbidden => result.Errors.Any()
? Result<TDestination>.Forbidden(result.Errors.ToArray())
: Result<TDestination>.Forbidden(),
ResultStatus.Invalid => Result<TDestination>.Invalid(result.ValidationErrors),
ResultStatus.Error => Result<TDestination>.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId)),
ResultStatus.Conflict => result.Errors.Any()
? Result<TDestination>.Conflict(result.Errors.ToArray())
: Result<TDestination>.Conflict(),
ResultStatus.CriticalError => Result<TDestination>.CriticalError(result.Errors.ToArray()),
ResultStatus.Unavailable => Result<TDestination>.Unavailable(result.Errors.ToArray()),
ResultStatus.NoContent => Result<TDestination>.NoContent(),
_ => throw new NotSupportedException($"Result {result.Status} conversion is not supported."),
};
}

private static Result<TDestination> HandleNonSuccessStatus<TDestination>(Result result)
{
return result.Status switch
{
ResultStatus.NotFound => result.Errors.Any()
? Result<TDestination>.NotFound(result.Errors.ToArray())
: Result<TDestination>.NotFound(),
ResultStatus.Unauthorized => result.Errors.Any()
? Result<TDestination>.Unauthorized(result.Errors.ToArray())
: Result<TDestination>.Unauthorized(),
ResultStatus.Forbidden => result.Errors.Any()
? Result<TDestination>.Forbidden(result.Errors.ToArray())
: Result<TDestination>.Forbidden(),
ResultStatus.Invalid => Result<TDestination>.Invalid(result.ValidationErrors),
ResultStatus.Error => Result<TDestination>.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId)),
ResultStatus.Conflict => result.Errors.Any()
? Result<TDestination>.Conflict(result.Errors.ToArray())
: Result<TDestination>.Conflict(),
ResultStatus.CriticalError => Result<TDestination>.CriticalError(result.Errors.ToArray()),
ResultStatus.Unavailable => Result<TDestination>.Unavailable(result.Errors.ToArray()),
ResultStatus.NoContent => Result<TDestination>.NoContent(),
_ => throw new NotSupportedException($"Result {result.Status} conversion is not supported."),
};
}

private static Result HandleNonSuccessStatus<TSource>(Result<TSource> result)
{
return result.Status switch
{
ResultStatus.NotFound => result.Errors.Any()
? Result.NotFound(result.Errors.ToArray())
: Result.NotFound(),
ResultStatus.Unauthorized => result.Errors.Any()
? Result.Unauthorized(result.Errors.ToArray())
: Result.Unauthorized(),
ResultStatus.Forbidden => result.Errors.Any()
? Result.Forbidden(result.Errors.ToArray())
: Result.Forbidden(),
ResultStatus.Invalid => Result.Invalid(result.ValidationErrors),
ResultStatus.Error => Result.Error(new ErrorList(result.Errors.ToArray(), result.CorrelationId)),
ResultStatus.Conflict => result.Errors.Any()
? Result.Conflict(result.Errors.ToArray())
: Result.Conflict(),
ResultStatus.CriticalError => Result.CriticalError(result.Errors.ToArray()),
ResultStatus.Unavailable => Result.Unavailable(result.Errors.ToArray()),
ResultStatus.NoContent => Result.NoContent(),
_ => throw new NotSupportedException($"Result {result.Status} conversion is not supported."),
};
}
}
}
Loading
Loading