namespace Just.Railway; public static class Ensure { public delegate Error ErrorFactory(string valueExpression); public const string DefaultErrorType = "EnsureFailed"; [Pure] public static Ensure That(T value, [CallerArgumentExpression(nameof(value))]string valueExpression = "") => new(value, valueExpression); [Pure] public static async Task> That(Task value, [CallerArgumentExpression(nameof(value))]string valueExpression = "") => new(await value.ConfigureAwait(false), valueExpression); [Pure] public static Result Result(this in Ensure ensure) => ensure.State switch { ResultState.Success => new(ensure.Value), ResultState.Error => new(ensure.Error!), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; [Pure] public static async Task> Result(this Task> ensureTask) { var ensure = await ensureTask.ConfigureAwait(false); return ensure.State switch { ResultState.Success => new(ensure.Value), ResultState.Error => new(ensure.Error!), _ => throw new EnsureNotInitializedException(nameof(ensureTask)) }; } [Pure] public static Ensure Satisfies(this in Ensure ensure, Func requirement, Error error = default!) { return ensure.State switch { ResultState.Success => requirement(ensure.Value) ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} does not satisfy the requirement."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static Ensure Satisfies(this in Ensure ensure, Func requirement, ErrorFactory errorFactory) { return ensure.State switch { ResultState.Success => requirement(ensure.Value) ? new(ensure.Value, ensure.ValueExpression) : new(errorFactory(ensure.ValueExpression), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static async Task> Satisfies(this Task> ensureTask, Func requirement, Error error = default!) { var ensure = await ensureTask.ConfigureAwait(false); return ensure.State switch { ResultState.Success => requirement(ensure.Value) ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} does not satisfy the requirement."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensureTask)) }; } [Pure] public static async Task> Satisfies(this Task> ensureTask, Func requirement, ErrorFactory errorFactory) { var ensure = await ensureTask.ConfigureAwait(false); return ensure.State switch { ResultState.Success => requirement(ensure.Value) ? new(ensure.Value, ensure.ValueExpression) : new(errorFactory(ensure.ValueExpression), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensureTask)) }; } [Pure] public static async Task> Satisfies(this Ensure ensure, Func> requirement, Error error = default!) { return ensure.State switch { ResultState.Success => await requirement(ensure.Value).ConfigureAwait(false) ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} does not satisfy the requirement."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static async Task> Satisfies(this Ensure ensure, Func> requirement, ErrorFactory errorFactory) { return ensure.State switch { ResultState.Success => await requirement(ensure.Value).ConfigureAwait(false) ? new(ensure.Value, ensure.ValueExpression) : new(errorFactory(ensure.ValueExpression), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static async Task> Satisfies(this Task> ensureTask, Func> requirement, Error error = default!) { var ensure = await ensureTask.ConfigureAwait(false); return ensure.State switch { ResultState.Success => await requirement(ensure.Value).ConfigureAwait(false) ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} does not satisfy the requirement."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensureTask)) }; } [Pure] public static async Task> Satisfies(this Task> ensureTask, Func> requirement, ErrorFactory errorFactory) { var ensure = await ensureTask.ConfigureAwait(false); return ensure.State switch { ResultState.Success => await requirement(ensure.Value).ConfigureAwait(false) ? new(ensure.Value, ensure.ValueExpression) : new(errorFactory(ensure.ValueExpression), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensureTask)) }; } [Pure] public static Ensure NotNull(this in Ensure ensure, Error error = default!) where T : struct { return ensure.State switch { ResultState.Success => ensure.Value.HasValue ? new(ensure.Value.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is null."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static async Task> NotNull(this Task> ensureTask, Error error = default!) where T : struct { var ensure = await ensureTask.ConfigureAwait(false); return ensure.State switch { ResultState.Success => ensure.Value.HasValue ? new(ensure.Value.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is null."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensureTask)) }; } [Pure] public static Ensure NotNull(this in Ensure ensure, Error error = default!) where T : notnull { return ensure.State switch { ResultState.Success => ensure.Value is not null ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is null."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static async Task> NotNull(this Task> ensureTask, Error error = default!) where T : notnull { var ensure = await ensureTask.ConfigureAwait(false); return ensure.State switch { ResultState.Success => ensure.Value is not null ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is null."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensureTask)) }; } [Pure] public static Ensure NotEmpty(this in Ensure ensure, Error error = default!) { return ensure.State switch { ResultState.Success => ensure.Value is not null && ensure.Value.Length > 0 ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is empty."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static Ensure> NotEmpty(this in Ensure> ensure, Error error = default!) { return ensure.State switch { ResultState.Success => ensure.Value is not null && ensure.Value.Count > 0 ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is empty."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static Ensure> NotEmpty(this in Ensure> ensure, Error error = default!) { return ensure.State switch { ResultState.Success => ensure.Value is not null && ensure.Value.Count > 0 ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is empty."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static Ensure> NotEmpty(this in Ensure> ensure, Error error = default!) { return ensure.State switch { ResultState.Success => ensure.Value is not null && ensure.Value.Count > 0 ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is empty."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static Ensure> NotEmpty(this in Ensure> ensure, Error error = default!) { return ensure.State switch { ResultState.Success => ensure.Value is not null && ensure.Value.Count > 0 ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is empty."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static Ensure> NotEmpty(this in Ensure> ensure, Error error = default!) { return ensure.State switch { ResultState.Success => ensure.Value is not null && ensure.Value.Count > 0 ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is empty."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static Ensure> NotEmpty(this in Ensure> ensure, Error error = default!) { return ensure.State switch { ResultState.Success => ensure.Value is not null && ensure.Value.Any() ? new(ensure.Value, ensure.ValueExpression) : new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is empty."), ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static Ensure NotEmpty(this in Ensure ensure, Error error = default!) { return ensure.State switch { ResultState.Success => string.IsNullOrEmpty(ensure.Value) ? new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is empty."), ensure.ValueExpression) : new(ensure.Value, ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } [Pure] public static Ensure NotWhitespace(this in Ensure ensure, Error error = default!) { return ensure.State switch { ResultState.Success => string.IsNullOrWhiteSpace(ensure.Value) ? new(error ?? Error.New(DefaultErrorType, $"Value {{{ensure.ValueExpression}}} is empty or consists exclusively of white-space characters."), ensure.ValueExpression) : new(ensure.Value, ensure.ValueExpression), ResultState.Error => new(ensure.Error!, ensure.ValueExpression), _ => throw new EnsureNotInitializedException(nameof(ensure)) }; } } public readonly struct Ensure { internal readonly ResultState State; internal readonly Error? Error; internal readonly T Value; internal readonly string ValueExpression; internal Ensure(T value, string valueExpression) { Value = value; ValueExpression = valueExpression; State = ResultState.Success; } internal Ensure(Error error, string valueExpression) { Error = error; ValueExpression = valueExpression; Value = default!; State = ResultState.Error; } } [Serializable] public class EnsureNotInitializedException(string variableName = "this") : InvalidOperationException("Ensure was not properly initialized.") { public string VariableName { get; } = variableName; }