diff --git a/src/Core/Ecommerce.Core/Structures/Either.cs b/src/Core/Ecommerce.Core/Structures/Either.cs new file mode 100644 index 0000000..15d8847 --- /dev/null +++ b/src/Core/Ecommerce.Core/Structures/Either.cs @@ -0,0 +1,126 @@ +namespace Ecommerce.Core.Structures; + +public record Either +{ + public Maybe Left { get; } + public Maybe Right { get; } + + public Either(TLeft value) + { + Left = Maybe.Of(value); + Right = Maybe.Empty; + } + + public Either(TRight value) + { + Left = Maybe.Empty; + Right = Maybe.Of(value); + } + + public Either(Maybe left, Maybe right) + { + if (!left.IsPresent && !right.IsPresent) + throw new ArgumentOutOfRangeException(nameof(right)); + + Left = left; + Right = right; + } + + public TMapped Map( + Func mapLeft, + Func mapRight) + { + if (Left.IsPresent) + return mapLeft(Left.GetOrThrow()); + + if (Right.IsPresent) + return mapRight(Right.GetOrThrow()); + + throw new Exception("This should not be possible"); + } + + public void Switch( + Action onLeft, + Action onRight) + { + if (Left.IsPresent) + { + onLeft(Left.GetOrThrow()); + return; + } + + if (Right.IsPresent) + { + onRight(Right.GetOrThrow()); + return; + } + + throw new Exception("This should not be possible"); + } +} + +public static class EitherExtensions +{ + public static (TLeft? Left, TRight? Right) AssertAnyDefined( + this (TLeft? Left, TRight? Right) value) + { + if (value.Left == null && value.Right == null) + throw new ArgumentOutOfRangeException(nameof(value), "A value has not been set"); + + return value; + } + + public static TMapped Map( + this (TLeft? Left, TRight? Right) value, + Func mapLeft, + Func mapRight) + where TLeft: struct + where TRight: struct + { + var (left, right) = value.AssertAnyDefined(); + + if (left.HasValue) + return mapLeft(left.Value); + + if (right.HasValue) + return mapRight(right.Value); + + throw new Exception("This should not be possible"); + } + + public static TMapped Map( + this (TLeft? Left, TRight? Right) value, + Func mapT1, + Func mapT2) + { + value.AssertAnyDefined(); + + var either = value.Left != null + ? new Either(value.Left!) + : new Either(value.Right!); + + return either.Map(mapT1, mapT2); + } + + public static void Switch( + this (TLeft? Left, TRight? Right) value, + Action onT1, + Action onT2) + { + value.AssertAnyDefined(); + + var either = value.Left != null + ? new Either(value.Left!) + : new Either(value.Right!); + + either.Switch(onT1, onT2); + } + + public static (TLeft?, TRight?) Either( + TLeft? left = default) + => (left, default); + + public static (TLeft?, TRight?) Either( + TRight? right = default) + => (default, right); +} diff --git a/src/Core/Ecommerce.Core/Structures/Maybe.cs b/src/Core/Ecommerce.Core/Structures/Maybe.cs new file mode 100644 index 0000000..23fd50c --- /dev/null +++ b/src/Core/Ecommerce.Core/Structures/Maybe.cs @@ -0,0 +1,44 @@ +namespace Ecommerce.Core.Structures; + +public record Maybe +{ + private readonly TSomething? _value; + public bool IsPresent { get; } + + private Maybe(TSomething value, bool isPresent) + { + _value = value; + IsPresent = isPresent; + } + + public static readonly Maybe Empty = new(default!, false); + + public static Maybe Of(TSomething value) => + value != null ? new Maybe(value, true) : Empty; + + public static Maybe If(bool check, Func getValue) => + check ? new Maybe(getValue(), true) : Empty; + + public TSomething GetOrThrow() => + IsPresent ? _value! : throw new ArgumentNullException(nameof(_value)); + + public TSomething GetOrDefault(TSomething defaultValue = default!) => + IsPresent ? _value ?? defaultValue : defaultValue; + + public void IfExists(Action perform) + { + if (IsPresent) + { + perform(_value!); + } + } +} + +public static class Maybe +{ + public static Maybe Of(TSomething value) => + Maybe.Of(value); + + public static Maybe If(bool check, Func getValue) => + Maybe.If(check, getValue); +} diff --git a/src/Core/Ecommerce.Core/Structures/OneOf.cs b/src/Core/Ecommerce.Core/Structures/OneOf.cs new file mode 100644 index 0000000..5833e06 --- /dev/null +++ b/src/Core/Ecommerce.Core/Structures/OneOf.cs @@ -0,0 +1,106 @@ +namespace Ecommerce.Core.Structures; + +public record OneOf +{ + public Maybe First { get; } + public Maybe Second { get; } + public Maybe Third { get; } + + public OneOf(T1 value) + { + First = Maybe.Of(value); + Second = Maybe.Empty; + Third = Maybe.Empty; + } + + public OneOf(T2 value) + { + First = Maybe.Empty; + Second = Maybe.Of(value); + Third = Maybe.Empty; + } + + public OneOf(T3 value) + { + First = Maybe.Empty; + Second = Maybe.Empty; + Third = Maybe.Of(value); + } + + public OneOf((T1? First, T2? Second, T3? Third) value) + { + First = value.First != null ? Maybe.Of(value.First) : Maybe.Empty; + Second = value.Second != null ? Maybe.Of(value.Second) : Maybe.Empty; + Third = value.Third != null ? Maybe.Of(value.Third) : Maybe.Empty; + } + + public TMapped Map( + Func mapT1, + Func mapT2, + Func mapT3) + { + if (First.IsPresent) + { + return mapT1(First.GetOrThrow()); + } + + if (Second.IsPresent) + { + return mapT2(Second.GetOrThrow()); + } + + if (Third.IsPresent) + { + return mapT3(Third.GetOrThrow()); + } + + throw new Exception("This should not be possible"); + } + + public void Switch( + Action onT1, + Action onT2, + Action onT3) + { + if (First.IsPresent) + { + onT1(First.GetOrThrow()); + return; + } + + if (Second.IsPresent) + { + onT2(Second.GetOrThrow()); + return; + } + + if (Third.IsPresent) + { + onT3(Third.GetOrThrow()); + return; + } + + throw new Exception("This should not be possible"); + } +} + +public static class OneOfExtensions +{ + public static void Map( + this (T1? First, T2? Second, T3? Third) value, + Func mapT1, + Func mapT2, + Func mapT3) + { + new OneOf(value).Map(mapT1, mapT2, mapT3); + } + + public static void Switch( + this (T1? First, T2? Second, T3? Third) value, + Action onT1, + Action onT2, + Action onT3) + { + new OneOf(value).Switch(onT1, onT2, onT3); + } +} diff --git a/src/Core/Ecommerce.Core/Structures/Result.cs b/src/Core/Ecommerce.Core/Structures/Result.cs new file mode 100644 index 0000000..9a83424 --- /dev/null +++ b/src/Core/Ecommerce.Core/Structures/Result.cs @@ -0,0 +1,79 @@ +namespace Ecommerce.Core.Structures; + +public record Result +{ + public Maybe Success { get; } + public Maybe Error { get; } + + public Result(TSuccess value) + { + Success = Maybe.Of(value); + Error = Maybe.Empty; + } + + public Result(TError value) + { + Success = Maybe.Empty; + Error = Maybe.Of(value); + } + + public Result(Maybe success, Maybe error) + { + if (!success.IsPresent && !error.IsPresent) + throw new ArgumentOutOfRangeException(nameof(error)); + + Success = success; + Error = error; + } + + public TMapped Map( + Func mapSuccess, + Func mapFailure + ) + { + if (Success.IsPresent) + return mapSuccess(Success.GetOrThrow()); + + if (Error.IsPresent) + return mapFailure(Error.GetOrThrow()); + + throw new Exception("That should never happen!"); + } + + public object FlatMap() + { + if (Success.IsPresent) + return Success.GetOrThrow()!; + + if (Error.IsPresent) + return Error.GetOrThrow()!; + + throw new Exception("That should never happen!"); + } + + public void Switch( + Action onSuccess, + Action onFailure) + { + if (Success.IsPresent) + { + onSuccess(Success.GetOrThrow()); + return; + } + + if (Error.IsPresent) + { + onFailure(Error.GetOrThrow()); + return; + } + + throw new Exception("That should never happen!"); + } +} + +public static class Result +{ + public static Result Success(TSuccess success) => new(success); + + public static Result Failure(TError error) => new(error); +}