From 3438743e6dacf6032f8344c9bba00809497fb2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20G=C3=A9linas?= Date: Tue, 22 May 2018 22:26:56 -0400 Subject: [PATCH 1/9] Implement the 'Option' class and the 'Result' class --- .../OptionAdaptersTests.cs | 147 +++++++++ src/SolidStack.Core.Flow.Tests/OptionTests.cs | 278 ++++++++++++++++++ src/SolidStack.Core.Flow.Tests/ResultTests.cs | 254 ++++++++++++++++ .../SolidStack.Core.Flow.Tests.csproj | 21 ++ src/SolidStack.Core.Flow/IOption.cs | 33 +++ src/SolidStack.Core.Flow/IResult.cs | 34 +++ .../Internal/ActionableContent.cs | 47 +++ .../Internal/ActionableVoid.cs | 33 +++ .../Internal/FilteredActionableContent.cs | 28 ++ .../Internal/FilteredActionableVoid.cs | 28 ++ .../Internal/FilteredMappableContent.cs | 37 +++ .../Internal/FilteredMappableVoid.cs | 35 +++ .../Internal/IActionable.cs | 10 + .../Internal/IFilteredActionableContent.cs | 18 ++ .../Internal/IFilteredActionableVoid.cs | 18 ++ .../Internal/IFilteredMappableContent`2.cs | 17 ++ .../Internal/IFilteredMappableContent`3.cs | 17 ++ .../Internal/IFilteredMappableVoid`1.cs | 17 ++ .../Internal/IFilteredMappableVoid`2.cs | 17 ++ .../Internal/IMappable.cs | 10 + .../Internal/MappableContent.cs | 72 +++++ .../Internal/MappableVoid.cs | 50 ++++ .../Internal/Option/IActionableOption.cs | 30 ++ .../Internal/Option/IFilteredMappableNone.cs | 17 ++ .../Option/IFilteredMappablePredicatedSome.cs | 19 ++ .../Internal/Option/IFilteredMappableSome.cs | 18 ++ .../Internal/Option/IFilteredNone.cs | 8 + .../Option/IFilteredPredicatedSome.cs | 8 + .../Internal/Option/IFilteredSome.cs | 8 + .../Internal/Option/IMappableNone.cs | 24 ++ .../Option/IMappablePredicatedSome.cs | 25 ++ .../Internal/Option/IMappableSome.cs | 11 + .../Internal/Option/ResolvedActionableNone.cs | 26 ++ .../Internal/Option/ResolvedActionableSome.cs | 29 ++ .../Internal/Option/ResolvedFilteredNone.cs | 23 ++ .../Option/ResolvedFilteredPredicatedSome.cs | 38 +++ .../Internal/Option/ResolvedFilteredSome.cs | 28 ++ .../Internal/Option/ResolvedMappableNone.cs | 25 ++ .../Option/ResolvedMappablePredicatedSome.cs | 32 ++ .../Internal/Option/ResolvedMappableSome.cs | 22 ++ .../Internal/Option/UnresolvedFilteredNone.cs | 28 ++ .../UnresolvedFilteredPredicatedSome.cs | 24 ++ .../Internal/Option/UnresolvedFilteredSome.cs | 23 ++ .../Internal/Option/UnresolvedMappableNone.cs | 28 ++ .../UnresolvedMappablePredicatedSome.cs | 20 ++ .../Internal/Option/UnresolvedMappableSome.cs | 10 + .../Internal/Result/IActionableResult.cs | 27 ++ .../Internal/Result/IFilteredError.cs | 8 + .../Internal/Result/IFilteredMappableError.cs | 18 ++ .../Result/IFilteredMappableSpecificError.cs | 20 ++ .../Result/IFilteredMappableSuccess.cs | 18 ++ .../Internal/Result/IFilteredSpecificError.cs | 9 + .../Internal/Result/IFilteredSuccess.cs | 8 + .../Internal/Result/IMappableError.cs | 11 + .../Internal/Result/IMappableSpecificError.cs | 24 ++ .../Internal/Result/IMappableSuccess.cs | 18 ++ .../Result/ResolvedActionableError.cs | 31 ++ .../Result/ResolvedActionableSuccess.cs | 29 ++ .../Internal/Result/ResolvedFilteredError.cs | 29 ++ .../Result/ResolvedFilteredSpecificError.cs | 36 +++ .../Result/ResolvedFilteredSuccess.cs | 29 ++ .../Internal/Result/ResolvedMappableError.cs | 22 ++ .../Result/ResolvedMappableSpecificError.cs | 38 +++ .../Result/ResolvedMappableSuccess.cs | 29 ++ .../Result/UnresolvedFilteredError.cs | 29 ++ .../Result/UnresolvedFilteredSpecificError.cs | 30 ++ .../Result/UnresolvedFilteredSuccess.cs | 29 ++ .../Result/UnresolvedMappableError.cs | 22 ++ .../Result/UnresolvedMappableSpecificError.cs | 34 +++ .../Result/UnresolvedMappableSuccess.cs | 31 ++ src/SolidStack.Core.Flow/Option.cs | 31 ++ src/SolidStack.Core.Flow/OptionAdapters.cs | 222 ++++++++++++++ src/SolidStack.Core.Flow/Option`.cs | 75 +++++ src/SolidStack.Core.Flow/Result.cs | 59 ++++ .../SolidStack.Core.Flow.csproj | 11 + src/SolidStack.sln | 14 +- 76 files changed, 2735 insertions(+), 1 deletion(-) create mode 100644 src/SolidStack.Core.Flow.Tests/OptionAdaptersTests.cs create mode 100644 src/SolidStack.Core.Flow.Tests/OptionTests.cs create mode 100644 src/SolidStack.Core.Flow.Tests/ResultTests.cs create mode 100644 src/SolidStack.Core.Flow.Tests/SolidStack.Core.Flow.Tests.csproj create mode 100644 src/SolidStack.Core.Flow/IOption.cs create mode 100644 src/SolidStack.Core.Flow/IResult.cs create mode 100644 src/SolidStack.Core.Flow/Internal/ActionableContent.cs create mode 100644 src/SolidStack.Core.Flow/Internal/ActionableVoid.cs create mode 100644 src/SolidStack.Core.Flow/Internal/FilteredActionableContent.cs create mode 100644 src/SolidStack.Core.Flow/Internal/FilteredActionableVoid.cs create mode 100644 src/SolidStack.Core.Flow/Internal/FilteredMappableContent.cs create mode 100644 src/SolidStack.Core.Flow/Internal/FilteredMappableVoid.cs create mode 100644 src/SolidStack.Core.Flow/Internal/IActionable.cs create mode 100644 src/SolidStack.Core.Flow/Internal/IFilteredActionableContent.cs create mode 100644 src/SolidStack.Core.Flow/Internal/IFilteredActionableVoid.cs create mode 100644 src/SolidStack.Core.Flow/Internal/IFilteredMappableContent`2.cs create mode 100644 src/SolidStack.Core.Flow/Internal/IFilteredMappableContent`3.cs create mode 100644 src/SolidStack.Core.Flow/Internal/IFilteredMappableVoid`1.cs create mode 100644 src/SolidStack.Core.Flow/Internal/IFilteredMappableVoid`2.cs create mode 100644 src/SolidStack.Core.Flow/Internal/IMappable.cs create mode 100644 src/SolidStack.Core.Flow/Internal/MappableContent.cs create mode 100644 src/SolidStack.Core.Flow/Internal/MappableVoid.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/IActionableOption.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/IFilteredMappableNone.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/IFilteredMappablePredicatedSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/IFilteredMappableSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/IFilteredNone.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/IFilteredPredicatedSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/IFilteredSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/IMappableNone.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/IMappablePredicatedSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/IMappableSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/ResolvedActionableNone.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/ResolvedActionableSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/ResolvedFilteredNone.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/ResolvedFilteredPredicatedSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/ResolvedFilteredSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/ResolvedMappableNone.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/ResolvedMappablePredicatedSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/ResolvedMappableSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/UnresolvedFilteredNone.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/UnresolvedFilteredPredicatedSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/UnresolvedFilteredSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/UnresolvedMappableNone.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/UnresolvedMappablePredicatedSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Option/UnresolvedMappableSome.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/IActionableResult.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/IFilteredError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/IFilteredMappableError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/IFilteredMappableSpecificError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/IFilteredMappableSuccess.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/IFilteredSpecificError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/IFilteredSuccess.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/IMappableError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/IMappableSpecificError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/IMappableSuccess.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/ResolvedActionableError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/ResolvedActionableSuccess.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/ResolvedFilteredError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/ResolvedFilteredSpecificError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/ResolvedFilteredSuccess.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/ResolvedMappableError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/ResolvedMappableSpecificError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/ResolvedMappableSuccess.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/UnresolvedFilteredError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/UnresolvedFilteredSpecificError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/UnresolvedFilteredSuccess.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/UnresolvedMappableError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/UnresolvedMappableSpecificError.cs create mode 100644 src/SolidStack.Core.Flow/Internal/Result/UnresolvedMappableSuccess.cs create mode 100644 src/SolidStack.Core.Flow/Option.cs create mode 100644 src/SolidStack.Core.Flow/OptionAdapters.cs create mode 100644 src/SolidStack.Core.Flow/Option`.cs create mode 100644 src/SolidStack.Core.Flow/Result.cs create mode 100644 src/SolidStack.Core.Flow/SolidStack.Core.Flow.csproj diff --git a/src/SolidStack.Core.Flow.Tests/OptionAdaptersTests.cs b/src/SolidStack.Core.Flow.Tests/OptionAdaptersTests.cs new file mode 100644 index 0000000..3b4ac6c --- /dev/null +++ b/src/SolidStack.Core.Flow.Tests/OptionAdaptersTests.cs @@ -0,0 +1,147 @@ +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Xunit; + +namespace SolidStack.Core.Flow.Tests +{ + public class OptionAdaptersTests + { + [Fact] + public void AsEnumerable_WhenNone_ReturnsAnEmptySequence() + { + var option = Option.None(); + var sequence = option.AsEnumerable(); + + sequence.Should().BeEmpty(); + } + + [Fact] + public void AsEnumerable_WhenSome_ReturnsASequenceOfOneElement() + { + var option = Option.Some(5); + var sequence = option.AsEnumerable(); + + sequence.Should() + .ContainSingle(x => x == 5) + .And.HaveCount(1); + } + + [Fact] + public void TryFirst_WithEmptySequence_ReturnsNone() + { + var sequence = Enumerable.Empty(); + var option = sequence.TryFirst(); + + option.AsEnumerable().Should().BeEmpty(); + } + + [Fact] + public void TryFirst_WithMatchingPredicate_ReturnsSome() + { + var sequence = new[] {4, 5}; + var option = sequence.TryFirst(x => x < 5); + + option.AsEnumerable().Should().ContainSingle(x => x == 4); + } + + [Fact] + public void TryFirst_WithSequenceOfOneOrMoreElements_ReturnsSome() + { + var sequence = new[] {4, 5}; + var option = sequence.TryFirst(); + + option.AsEnumerable().Should().ContainSingle(x => x == 4); + } + + [Fact] + public void TryFirst_WithUnmatchingPredicate_ReturnsNone() + { + var sequence = Enumerable.Empty(); + var option = sequence.TryFirst(x => x >= 5); + + option.AsEnumerable().Should().BeEmpty(); + } + + [Fact] + public void TryGetValue_WithExistingKey_ReturnsSome() + { + var dictionary = new Dictionary {{"foo", 7}, {"bar", 3}}; + var option = dictionary.TryGetValue("foo"); + + option.AsEnumerable().Should().ContainSingle(x => x == 7); + } + + [Fact] + public void TryGetValue_WithUnexistingKey_ReturnsNone() + { + var dictionary = new Dictionary {{"foo", 7}, {"bar", 3}}; + var option = dictionary.TryGetValue("baz"); + + option.AsEnumerable().Should().BeEmpty(); + } + + [Fact] + public void TrySingle_WhenEmptySequence_ReturnsNone() + { + var sequence = Enumerable.Empty(); + var option = sequence.TrySingle(); + + option.AsEnumerable().Should().BeEmpty(); + } + + [Fact] + public void TrySingle_WithPredicateThatMatchManyElements_ReturnsNone() + { + var sequence = new[] {-2, 4, 5}; + var option = sequence.TrySingle(x => x < 5); + + option.AsEnumerable().Should().BeEmpty(); + } + + [Fact] + public void TrySingle_WithPredicateThatMatchOneElement_ReturnsSome() + { + var sequence = new[] {4, 5}; + var option = sequence.TrySingle(x => x < 5); + + option.AsEnumerable().Should().ContainSingle(x => x == 4); + } + + [Fact] + public void TrySingle_WithSequenceOfMoreThenOneElement_ReturnsNone() + { + var sequence = new[] {4, 5}; + var option = sequence.TrySingle(); + + option.AsEnumerable().Should().BeEmpty(); + } + + [Fact] + public void TrySingle_WithUnmatchingPredicate_ReturnsNone() + { + var sequence = Enumerable.Empty(); + var option = sequence.TrySingle(x => x >= 5); + + option.AsEnumerable().Should().BeEmpty(); + } + + [Fact] + public void WhereSome_FiltersTheEmptyOptions() + { + var options = new[] {Option.Some(4), Option.None(), Option.Some(5), Option.None()}; + var values = options.WhereSome(); + + values.Should().HaveCount(2).And.ContainInOrder(4, 5); + } + + [Fact] + public void WhereSome_WithPredicate_FiltersTheEmptyOptions() + { + var sequence = new[] {4, -1, 5, -3}; + var values = sequence.WhereSome(x => x >= 0 ? Option.Some(x) : Option.None()); + + values.Should().HaveCount(2).And.ContainInOrder(4, 5); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow.Tests/OptionTests.cs b/src/SolidStack.Core.Flow.Tests/OptionTests.cs new file mode 100644 index 0000000..2e5b68b --- /dev/null +++ b/src/SolidStack.Core.Flow.Tests/OptionTests.cs @@ -0,0 +1,278 @@ +using FluentAssertions; +using Xunit; + +namespace SolidStack.Core.Flow.Tests +{ + public class OptionTests + { + [Fact] + public void Do_OnMatchedWhereClause_FallThrough() + { + var count = 0; + var option = Option.Some(5); + + option + .When(x => x > 3) + .Do(x => count++) + .WhenSome() + .Do(x => count++) + .Execute(); + + count.Should().Be(2); + } + + [Fact] + public void Do_OnNone_FallThrough() + { + var count = 0; + var option = Option.None(); + + option + .WhenNone() + .Do(() => count++) + .WhenNone() + .Do(() => count++) + .Execute(); + + count.Should().Be(2); + } + + [Fact] + public void Do_OnSome_FallThrough() + { + var count = 0; + var option = Option.Some(5); + + option + .WhenSome() + .Do(x => count++) + .WhenSome() + .Do(x => count++) + .Execute(); + + count.Should().Be(2); + } + + [Fact] + public void Do_OnUnmatchedWhereClause_FallThrough() + { + var count = 0; + var option = Option.Some(5); + + option + .When(x => x < 3) + .Do(x => count++) + .WhenSome() + .Do(x => count++) + .Execute(); + + count.Should().Be(1); + } + + [Fact] + public void Do_WhenMatchesAndServesInputValueToLambda_ReturnsOptionValue() + { + var touched = false; + var value = 0; + var option = Option.Some(5); + + option + .When(x => x > 6) + .Do(x => touched = true) + .When(x => x > 3) + .Do(x => value = x) + .Execute(); + + touched.Should().BeFalse(); + value.Should().Be(5); + } + + [Fact] + public void Do_WhenSomeMatchesAndServesInputValueToLambda_ReturnsOptionValue() + { + var touched = false; + var value = 0; + var option = Option.Some(5); + + option + .When(x => x > 6) + .Do(x => touched = true) + .WhenSome() + .Do(x => value = x) + .Execute(); + + touched.Should().BeFalse(); + value.Should().Be(5); + } + + [Fact] + public void Execute_WhenDoesntMatchesSome_DoesntExecuteDoAction() + { + var touched = false; + var option = Option.Some(5); + + option + .When(x => x > 6) + .Do(x => touched = true) + .Execute(); + + touched.Should().BeFalse(); + } + + [Fact] + public void Execute_WhenMatchesSome_ExecuteDoAction() + { + var touched = false; + var option = Option.Some(5); + + option + .When(x => x > 3) + .Do(x => touched = true) + .Execute(); + + touched.Should().BeTrue(); + } + + [Fact] + public void Execute_WhenNoneMatchesNone_DoesntExecuteDoAction() + { + var touchedAfterWhen = false; + var touchedAfterWhenNone = false; + var option = Option.None(); + + option + .When(x => true) + .Do(x => touchedAfterWhen = true) + .WhenNone() + .Do(() => touchedAfterWhenNone = true) + .Execute(); + + touchedAfterWhen.Should().BeFalse(); + touchedAfterWhenNone.Should().BeTrue(); + } + + [Fact] + public void Execute_WhenNoneOfWhenMatchesSome_DoesntExecuteDoActions() + { + var touchedAfterFirstWhen = false; + var touchedAfterSecondWhen = false; + var option = Option.Some(5); + + option + .When(x => x > 6) + .Do(x => touchedAfterFirstWhen = true) + .When(x => x > 7) + .Do(x => touchedAfterSecondWhen = true) + .Execute(); + + touchedAfterFirstWhen.Should().BeFalse(); + touchedAfterSecondWhen.Should().BeFalse(); + } + + [Fact] + public void Execute_WhenNotMatchingNone_DoesntExecuteDoAction() + { + var touched = false; + var option = Option.None(); + + option + .When(x => true) + .Do(x => touched = true) + .Execute(); + + touched.Should().BeFalse(); + } + + [Fact] + public void Execute_WhenSomeMatched_ExecutesAllSomeAndWhereAction() + { + var touchedAfterWhen = false; + var touchedAfterWhenSome = false; + var option = Option.Some(5); + + option + .WhenSome() + .Do(x => touchedAfterWhenSome = true) + .When(x => x > 3) + .Do(x => touchedAfterWhen = true) + .Execute(); + + touchedAfterWhenSome.Should().BeTrue(); + touchedAfterWhen.Should().BeTrue(); + } + + [Fact] + public void MapTo_OnMatchedWhereClause_DoesntFallThrough() + { + var option = Option.Some(5); + + var result = + option + .When(x => x > 3) + .MapTo(x => $"{x} > 3") + .When(x => x > 2) + .MapTo(x => "x > 2") + .WhenSome() + .MapTo(x => "some") + .WhenNone() + .MapTo(() => "none") + .Map(); + + result.Should().Be("5 > 3"); + } + + [Fact] + public void MapTo_OnNone_ReturnsMappedResult() + { + var option = Option.None(); + + var result = + option + .When(x => x > 3) + .MapTo(x => "x > 3") + .WhenSome() + .MapTo(x => "some") + .WhenNone() + .MapTo(() => "none") + .Map(); + + result.Should().Be("none"); + } + + [Fact] + public void MapTo_OnSome_ReturnsMappedResult() + { + var option = Option.Some(5); + + var result = + option + .WhenSome() + .MapTo(x => "some") + .WhenNone() + .MapTo(() => "none") + .Map(); + + result.Should().Be("some"); + } + + [Fact] + public void MapTo_OnUnmatchedWhereClause_DoesntFallThrough() + { + var option = Option.Some(5); + + var result = + option + .When(x => x != 5) + .MapTo(x => $"x != 5") + .When(x => x > 2) + .MapTo(x => "x > 2") + .WhenSome() + .MapTo(x => "some") + .WhenNone() + .MapTo(() => "none") + .Map(); + + result.Should().Be("x > 2"); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow.Tests/ResultTests.cs b/src/SolidStack.Core.Flow.Tests/ResultTests.cs new file mode 100644 index 0000000..8cbd541 --- /dev/null +++ b/src/SolidStack.Core.Flow.Tests/ResultTests.cs @@ -0,0 +1,254 @@ +using FluentAssertions; +using Xunit; + +namespace SolidStack.Core.Flow.Tests +{ + public class ResultTests + { + [Fact] + public void Do_OnError_FallThrough() + { + var count = 0; + var result = Result.Error("error"); + + result + .WhenError() + .Do(x => count++) + .WhenError() + .Do(x => count++) + .Execute(); + + count.Should().Be(2); + } + + [Fact] + public void Do_OnSuccess_FallThrough() + { + var count = 0; + var result = Result.Success("success"); + + result + .WhenSuccess() + .Do(x => count++) + .WhenSuccess() + .Do(x => count++) + .Execute(); + + count.Should().Be(2); + } + + [Fact] + public void Do_WhenErrorMatchesAndServesInputValueToLambda_ReturnsErrorValue() + { + var value = ""; + var result = Result.Error("error"); + + result + .WhenError() + .Do(x => value = x) + .Execute(); + + value.Should().Be("error"); + } + + [Fact] + public void Do_WhenSpecificErrorMatchesAndServesInputValueToLambda_ReturnsSpecificErrorValue() + { + var value = ""; + var result = Result.Error("error"); + + result + .WhenError() + .Do(x => value = x) + .Execute(); + + value.Should().Be("error"); + } + + [Fact] + public void Do_WhenSuccessMatchesAndServesInputValueToLambda_ReturnsSuccessValue() + { + var value = ""; + var result = Result.Success("success"); + + result + .WhenSuccess() + .Do(x => value = x) + .Execute(); + + value.Should().Be("success"); + } + + [Fact] + public void Execute_WhenErrorDoesntMatches_DoesntExecuteDoAction() + { + var touched = false; + var result = Result.Success("success"); + + result + .WhenError() + .Do(x => touched = true) + .Execute(); + + touched.Should().BeFalse(); + } + + [Fact] + public void Execute_WhenErrorMatches_ExecuteDoAction() + { + var touched = false; + var result = Result.Error("error"); + + result + .WhenError() + .Do(x => touched = true) + .Execute(); + + touched.Should().BeTrue(); + } + + [Fact] + public void Execute_WhenSpecificErrorDoesntMatches_DoesntExecuteDoAction() + { + var touched = false; + var result = Result.Error("error"); + + result + .WhenError() + .Do(x => touched = true) + .Execute(); + + touched.Should().BeFalse(); + } + + [Fact] + public void Execute_WhenSpecificErrorMatches_ExecuteDoAction() + { + var touched = false; + var result = Result.Error("error"); + + result + .WhenError() + .Do(x => touched = true) + .Execute(); + + touched.Should().BeTrue(); + } + + [Fact] + public void Execute_WhenSuccessDoesntMatches_DoesntExecuteDoAction() + { + var touched = false; + var result = Result.Error("error"); + + result + .WhenSuccess() + .Do(x => touched = true) + .Execute(); + + touched.Should().BeFalse(); + } + + [Fact] + public void Execute_WhenSuccessMatches_ExecuteDoAction() + { + var touched = false; + var result = Result.Success("success"); + + result + .WhenSuccess() + .Do(x => touched = true) + .Execute(); + + touched.Should().BeTrue(); + } + + [Fact] + public void MapTo_OnError_ReturnsMappedResult() + { + var result = Result.Error("error"); + + var mappedResult = + result + .WhenError() + .MapTo(x => 2) + .WhenError() + .MapTo(x => 1) + .WhenSuccess() + .MapTo(x => 0) + .Map(); + + mappedResult.Should().Be(1); + } + + [Fact] + public void MapTo_OnMatchedSpecificError_DoesntFallThrough() + { + var result = Result.Error("error"); + + var mappedResult = + result + .WhenError() + .MapTo(x => "string error") + .WhenError() + .MapTo(x => "error") + .WhenSuccess() + .MapTo(x => "success") + .Map(); + + mappedResult.Should().Be("string error"); + } + + [Fact] + public void MapTo_OnSpecificError_ReturnsMappedResult() + { + var result = Result.Error("error"); + + var mappedResult = + result + .WhenError() + .MapTo(x => 2) + .WhenError() + .MapTo(x => 1) + .WhenSuccess() + .MapTo(x => 0) + .Map(); + + mappedResult.Should().Be(2); + } + + [Fact] + public void MapTo_OnSuccess_ReturnsMappedResult() + { + var result = Result.Success("success"); + + var mappedResult = + result + .WhenSuccess() + .MapTo(x => 0) + .WhenError() + .MapTo(x => 1) + .Map(); + + mappedResult.Should().Be(0); + } + + [Fact] + public void MapTo_UnmatchedSpecificError_DoesntFallThrough() + { + var result = Result.Error("error"); + + var mappedResult = + result + .WhenError() + .MapTo(x => "int error") + .WhenError() + .MapTo(x => "error") + .WhenSuccess() + .MapTo(x => "success") + .Map(); + + mappedResult.Should().Be("error"); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow.Tests/SolidStack.Core.Flow.Tests.csproj b/src/SolidStack.Core.Flow.Tests/SolidStack.Core.Flow.Tests.csproj new file mode 100644 index 0000000..d33429e --- /dev/null +++ b/src/SolidStack.Core.Flow.Tests/SolidStack.Core.Flow.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp2.0 + + false + + + + + + + + + + + + + + + diff --git a/src/SolidStack.Core.Flow/IOption.cs b/src/SolidStack.Core.Flow/IOption.cs new file mode 100644 index 0000000..3484da0 --- /dev/null +++ b/src/SolidStack.Core.Flow/IOption.cs @@ -0,0 +1,33 @@ +using System; +using SolidStack.Core.Flow.Internal.Option; + +namespace SolidStack.Core.Flow +{ + /// + /// Represents a value that may or may not be present and provides methods for building and then executing an + /// easy-to-read linear flow around these two situations. + /// + /// The type of the value that may or may not be present. + public interface IOption + { + /// + /// Filters the current flow to be able to create actions that will be executed only when there is a value and that + /// value matches the given predicate. + /// + /// The predicate to match against the value. + /// The filtered flow containing the following possible actions that can be performed on the optional value. + IFilteredPredicatedSome When(Func predicate); + + /// + /// Filters the current flow to be able to create actions that will be executed only when there is no value. + /// + /// The filtered flow containing the following possible actions that can be performed on the optional value. + IFilteredNone WhenNone(); + + /// + /// Filters the current flow to be able to create actions that will be executed only when there is a value. + /// + /// The filtered flow containing the following possible actions that can be performed on the optional value. + IFilteredSome WhenSome(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/IResult.cs b/src/SolidStack.Core.Flow/IResult.cs new file mode 100644 index 0000000..5c53756 --- /dev/null +++ b/src/SolidStack.Core.Flow/IResult.cs @@ -0,0 +1,34 @@ +using SolidStack.Core.Flow.Internal.Result; + +namespace SolidStack.Core.Flow +{ + /// + /// Represents the result of an operation that may end in a success or an error and provides methods for building and + /// then executing an easy-to-read linear flow around these two situations. + /// + /// The type of error that can be returned. + /// The type of success that can be returned. + public interface IResult + { + /// + /// Filters the current flow to be able to create actions that will be executed only when the result is an error and + /// that error is assignable to the given specific error type. + /// + /// The type of the specific error matched against the actual error. + /// The filtered flow containing the following possible actions that can be performed on the result value. + IFilteredSpecificError WhenError() + where TSpecificError : TError; + + /// + /// Filters the current flow to be able to create actions that will be executed only when the result is an error. + /// + /// The filtered flow containing the following possible actions that can be performed on the result value. + IFilteredError WhenError(); + + /// + /// Filters the current flow to be able to create actions that will be executed only when the result is a success. + /// + /// The filtered flow containing the following possible actions that can be performed on the result value. + IFilteredSuccess WhenSuccess(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/ActionableContent.cs b/src/SolidStack.Core.Flow/Internal/ActionableContent.cs new file mode 100644 index 0000000..b492f09 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/ActionableContent.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SolidStack.Core.Flow.Internal +{ + internal abstract class ActionableContent : + IActionable + where TSelf : ActionableContent + { + protected ActionableContent(TContent content, params Action[] actions) : + this(content, actions.AsEnumerable()) + { + } + + protected ActionableContent(TContent content, IEnumerable> actions) + { + Content = content; + Actions = actions.ToList(); + } + + protected TContent Content { get; } + + private List> Actions { get; } + + public void Execute() => + Actions.ForEach(action => action(Content)); + + protected IFilteredActionableContent AppendNextAction() => + new FilteredActionableContent((TSelf) this, Actions.Add); + + protected IFilteredActionableContent AppendNextAction() + where TSpecificContent : TContent => + new FilteredActionableContent( + (TSelf) this, + action => action((TSpecificContent) Content)); + + protected IFilteredActionableContent SkipNextAction() => + new FilteredActionableContent((TSelf) this, _ => { }); + + protected IFilteredActionableContent SkipNextAction() => + new FilteredActionableContent((TSelf) this, _ => { }); + + protected IFilteredActionableVoid SkipNextVoidAction() => + new FilteredActionableVoid((TSelf) this, _ => { }); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/ActionableVoid.cs b/src/SolidStack.Core.Flow/Internal/ActionableVoid.cs new file mode 100644 index 0000000..49ad70c --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/ActionableVoid.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SolidStack.Core.Flow.Internal +{ + internal abstract class ActionableVoid : + IActionable + where TSelf : ActionableVoid + { + protected ActionableVoid(params Action[] actions) : + this(actions.AsEnumerable()) + { + } + + protected ActionableVoid(IEnumerable actions) => + Actions = actions.ToList(); + + private List Actions { get; } + + public void Execute() => + Actions.ForEach(action => action()); + + protected IFilteredActionableVoid AppendNextAction() => + new FilteredActionableVoid((TSelf) this, Actions.Add); + + protected IFilteredActionableVoid SkipNextAction() => + new FilteredActionableVoid((TSelf) this, _ => { }); + + protected IFilteredActionableContent SkipNextContentAction() => + new FilteredActionableContent((TSelf) this, _ => { }); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/FilteredActionableContent.cs b/src/SolidStack.Core.Flow/Internal/FilteredActionableContent.cs new file mode 100644 index 0000000..d8073ef --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/FilteredActionableContent.cs @@ -0,0 +1,28 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal +{ + internal class FilteredActionableContent : + IFilteredActionableContent + where TActionable : IActionable + { + public FilteredActionableContent(TActionable actionable, Action> handleAction) + { + Actionable = actionable; + HandleAction = handleAction; + } + + private TActionable Actionable { get; } + + private Action> HandleAction { get; } + + public TActionable Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + HandleAction(action); + return Actionable; + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/FilteredActionableVoid.cs b/src/SolidStack.Core.Flow/Internal/FilteredActionableVoid.cs new file mode 100644 index 0000000..8bb9a61 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/FilteredActionableVoid.cs @@ -0,0 +1,28 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal +{ + internal class FilteredActionableVoid : + IFilteredActionableVoid + where TActionable : IActionable + { + public FilteredActionableVoid(TActionable actionable, Action handleAction) + { + Actionable = actionable; + HandleAction = handleAction; + } + + private TActionable Actionable { get; } + + private Action HandleAction { get; } + + public TActionable Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + HandleAction(action); + return Actionable; + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/FilteredMappableContent.cs b/src/SolidStack.Core.Flow/Internal/FilteredMappableContent.cs new file mode 100644 index 0000000..11b48c1 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/FilteredMappableContent.cs @@ -0,0 +1,37 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal +{ + internal class FilteredMappableContent : + IFilteredMappableContent, + IFilteredMappableContent + where TMappable : IMappable + { + public FilteredMappableContent(TMappable mappable, Action> handleMapping) + { + Mappable = mappable; + HandleMapping = handleMapping; + } + + private Action> HandleMapping { get; } + + private TMappable Mappable { get; } + + TMappable IFilteredMappableContent.MapTo( + Func mapping) => + MapTo(mapping); + + IMappable IFilteredMappableContent.MapTo( + Func mapping) => + MapTo(mapping); + + private TMappable MapTo(Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + HandleMapping(mapping); + return Mappable; + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/FilteredMappableVoid.cs b/src/SolidStack.Core.Flow/Internal/FilteredMappableVoid.cs new file mode 100644 index 0000000..fede004 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/FilteredMappableVoid.cs @@ -0,0 +1,35 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal +{ + internal class FilteredMappableVoid : + IFilteredMappableVoid, + IFilteredMappableVoid + where TMappable : IMappable + { + public FilteredMappableVoid(TMappable mappable, Action> handleMapping) + { + Mappable = mappable; + HandleMapping = handleMapping; + } + + private Action> HandleMapping { get; } + + private TMappable Mappable { get; } + + TMappable IFilteredMappableVoid.MapTo(Func mapping) => + MapTo(mapping); + + IMappable IFilteredMappableVoid.MapTo(Func mapping) => + MapTo(mapping); + + private TMappable MapTo(Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + HandleMapping(mapping); + return Mappable; + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/IActionable.cs b/src/SolidStack.Core.Flow/Internal/IActionable.cs new file mode 100644 index 0000000..8144d02 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/IActionable.cs @@ -0,0 +1,10 @@ +namespace SolidStack.Core.Flow.Internal +{ + public interface IActionable + { + /// + /// Executes all actions added to the filtered flow corresponding to the initial object state. + /// + void Execute(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/IFilteredActionableContent.cs b/src/SolidStack.Core.Flow/Internal/IFilteredActionableContent.cs new file mode 100644 index 0000000..93efe16 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/IFilteredActionableContent.cs @@ -0,0 +1,18 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal +{ + public interface IFilteredActionableContent + where TActionable : IActionable + { + /// + /// Adds an action to the current filtered flow that will be executed further if the execution flow match the current + /// filtered flow. + /// + /// The action to add. + /// The updated flow. + /// If the given action is null. + TActionable Do(Action action); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/IFilteredActionableVoid.cs b/src/SolidStack.Core.Flow/Internal/IFilteredActionableVoid.cs new file mode 100644 index 0000000..6fa1451 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/IFilteredActionableVoid.cs @@ -0,0 +1,18 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal +{ + public interface IFilteredActionableVoid + where TActionable : IActionable + { + /// + /// Adds an action to the current filtered flow that will be executed further if the execution flow match the current + /// filtered flow. + /// + /// The action to add. + /// The updated flow. + /// If the given action is null. + TActionable Do(Action action); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/IFilteredMappableContent`2.cs b/src/SolidStack.Core.Flow/Internal/IFilteredMappableContent`2.cs new file mode 100644 index 0000000..c2c812d --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/IFilteredMappableContent`2.cs @@ -0,0 +1,17 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal +{ + public interface IFilteredMappableContent + { + /// + /// Adds a mapping action to the current filtered flow that will be executed further if the initial object state + /// correspond to the current filtered flow. + /// + /// The mapping action to use. + /// The updated flow. + /// If the given mapping action is null. + IMappable MapTo(Func mapping); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/IFilteredMappableContent`3.cs b/src/SolidStack.Core.Flow/Internal/IFilteredMappableContent`3.cs new file mode 100644 index 0000000..f9a69f1 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/IFilteredMappableContent`3.cs @@ -0,0 +1,17 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal +{ + public interface IFilteredMappableContent + { + /// + /// Adds a mapping action to the current filtered flow that will be executed further if the initial object state + /// correspond to the current filtered flow. + /// + /// The mapping action to use. + /// The updated flow. + /// If the given mapping action is null. + TMappable MapTo(Func mapping); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/IFilteredMappableVoid`1.cs b/src/SolidStack.Core.Flow/Internal/IFilteredMappableVoid`1.cs new file mode 100644 index 0000000..66ba069 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/IFilteredMappableVoid`1.cs @@ -0,0 +1,17 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal +{ + public interface IFilteredMappableVoid + { + /// + /// Adds a mapping action to the current filtered flow that will be executed further if the initial object state + /// correspond to the current filtered flow. + /// + /// The mapping action to use. + /// The updated flow. + /// If the given mapping action is null. + IMappable MapTo(Func mapping); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/IFilteredMappableVoid`2.cs b/src/SolidStack.Core.Flow/Internal/IFilteredMappableVoid`2.cs new file mode 100644 index 0000000..6b0fb3e --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/IFilteredMappableVoid`2.cs @@ -0,0 +1,17 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal +{ + public interface IFilteredMappableVoid + { + /// + /// Adds a mapping action to the current filtered flow that will be executed further if the initial object state + /// correspond to the current filtered flow. + /// + /// The mapping action to use. + /// The updated flow. + /// If the given mapping action is null. + TMappable MapTo(Func mapping); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/IMappable.cs b/src/SolidStack.Core.Flow/Internal/IMappable.cs new file mode 100644 index 0000000..739aa4c --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/IMappable.cs @@ -0,0 +1,10 @@ +namespace SolidStack.Core.Flow.Internal +{ + public interface IMappable + { + /// + /// Maps the initial object using the filtered flow corresponding to the initial object state. + /// + TDestination Map(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/MappableContent.cs b/src/SolidStack.Core.Flow/Internal/MappableContent.cs new file mode 100644 index 0000000..4b3951a --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/MappableContent.cs @@ -0,0 +1,72 @@ +using System; + +namespace SolidStack.Core.Flow.Internal +{ + internal class MappableContent : + IMappable + where TSelf : MappableContent + { + public MappableContent(TContent content) + { + Content = content; + Mapping = _ => throw new InvalidOperationException(); + TryUseMapping = mapping => + { + Mapping = mapping; + TryUseMapping = _ => { }; + }; + } + + public MappableContent(TContent content, Func mapping) + { + Content = content; + Mapping = mapping; + TryUseMapping = _ => { }; + } + + protected TContent Content { get; } + + private Func Mapping { get; set; } + + private Action> TryUseMapping { get; set; } + + public TDestination Map() => + Mapping(Content); + + protected IFilteredMappableContent SkipLastMapping() => + new FilteredMappableContent((TSelf) this, _ => { }); + + protected IFilteredMappableContent SkipLastMapping() => + new FilteredMappableContent((TSelf) this, _ => { }); + + protected IFilteredMappableVoid SkipLastVoidMapping() => + new FilteredMappableVoid((TSelf) this, _ => { }); + + protected IFilteredMappableContent SkipNextMapping() => + new FilteredMappableContent((TSelf) this, _ => { }); + + protected IFilteredMappableContent SkipNextMapping() => + new FilteredMappableContent((TSelf) this, _ => { }); + + protected IFilteredMappableVoid SkipNextVoidMapping() => + new FilteredMappableVoid((TSelf) this, _ => { }); + + protected IFilteredMappableContent TryUseLastMapping() => + new FilteredMappableContent((TSelf) this, TryUseMapping); + + protected IFilteredMappableContent TryUseLastMapping() + where TSpecificContent : TContent => + new FilteredMappableContent( + (TSelf) this, + func => func((TSpecificContent) Content)); + + protected IFilteredMappableContent TryUseNextMapping() => + new FilteredMappableContent((TSelf) this, TryUseMapping); + + protected IFilteredMappableContent TryUseNextMapping() + where TSpecificContent : TContent => + new FilteredMappableContent( + (TSelf) this, + func => func((TSpecificContent) Content)); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/MappableVoid.cs b/src/SolidStack.Core.Flow/Internal/MappableVoid.cs new file mode 100644 index 0000000..5c38dab --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/MappableVoid.cs @@ -0,0 +1,50 @@ +using System; + +namespace SolidStack.Core.Flow.Internal +{ + internal abstract class MappableVoid : + IMappable + where TSelf : MappableVoid + { + protected MappableVoid() + { + Mapping = () => throw new InvalidOperationException(); + TryUseMapping = mapping => + { + Mapping = mapping; + TryUseMapping = _ => { }; + }; + } + + protected MappableVoid(Func mapping) + { + Mapping = mapping; + TryUseMapping = _ => { }; + } + + private Func Mapping { get; set; } + + private Action> TryUseMapping { get; set; } + + public TDestination Map() => + Mapping(); + + protected IFilteredMappableContent SkipLastContentMapping() => + new FilteredMappableContent((TSelf) this, _ => { }); + + protected IFilteredMappableVoid SkipLastMapping() => + new FilteredMappableVoid((TSelf) this, _ => { }); + + protected IFilteredMappableContent SkipNextContentMapping() => + new FilteredMappableContent((TSelf) this, _ => { }); + + protected IFilteredMappableVoid SkipNextMapping() => + new FilteredMappableVoid((TSelf) this, _ => { }); + + protected IFilteredMappableVoid TryUseLastMapping() => + new FilteredMappableVoid((TSelf) this, TryUseMapping); + + protected IFilteredMappableVoid TryUseNextMapping() => + new FilteredMappableVoid((TSelf) this, TryUseMapping); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/IActionableOption.cs b/src/SolidStack.Core.Flow/Internal/Option/IActionableOption.cs new file mode 100644 index 0000000..86c1bbd --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/IActionableOption.cs @@ -0,0 +1,30 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Option +{ + public interface IActionableOption : + IActionable + { + /// + /// Filters the current flow to be able to create actions that will be executed only when there is a value and that + /// value matches the given predicate. + /// + /// The predicate to match against the value. + /// The filtered flow containing the following possible actions that can be performed on the optional value. + /// If the given predicate is null. + IFilteredActionableContent> When(Func predicate); + + /// + /// Filters the current flow to be able to create actions that will be executed only when there is no value. + /// + /// The filtered flow containing the following possible actions that can be performed on the optional value. + IFilteredActionableVoid> WhenNone(); + + /// + /// Filters the current flow to be able to create actions that will be executed only when there is a value. + /// + /// The filtered flow containing the following possible actions that can be performed on the optional value. + IFilteredActionableContent> WhenSome(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/IFilteredMappableNone.cs b/src/SolidStack.Core.Flow/Internal/Option/IFilteredMappableNone.cs new file mode 100644 index 0000000..76c371c --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/IFilteredMappableNone.cs @@ -0,0 +1,17 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Option +{ + public interface IFilteredMappableNone + { + /// + /// Adds a mapping action to the current filtered flow that will be executed further if the initial option contains no value. + /// + /// The destination type to create. + /// The mapping action to use. + /// The updated flow. + /// If the given mapping action is null. + IMappableNone MapTo(Func mapping); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/IFilteredMappablePredicatedSome.cs b/src/SolidStack.Core.Flow/Internal/Option/IFilteredMappablePredicatedSome.cs new file mode 100644 index 0000000..5393a2d --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/IFilteredMappablePredicatedSome.cs @@ -0,0 +1,19 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Option +{ + public interface IFilteredMappablePredicatedSome + { + /// + /// Adds a mapping action to the current filtered flow that will be executed further if the initial option contains a + /// value and that value matches the given predicate. + /// + /// The destination type to create. + /// The mapping action to use. + /// The updated flow. + /// If the given predicate is null. + IMappablePredicatedSome + MapTo(Func mapping); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/IFilteredMappableSome.cs b/src/SolidStack.Core.Flow/Internal/Option/IFilteredMappableSome.cs new file mode 100644 index 0000000..c07120e --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/IFilteredMappableSome.cs @@ -0,0 +1,18 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Option +{ + public interface IFilteredMappableSome + { + /// + /// Adds a mapping action to the current filtered flow that will be executed further if the initial option contains a + /// value. + /// + /// The destination type to create. + /// The mapping action to use. + /// The updated flow. + /// If the given predicate is null. + IMappableSome MapTo(Func mapping); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/IFilteredNone.cs b/src/SolidStack.Core.Flow/Internal/Option/IFilteredNone.cs new file mode 100644 index 0000000..3227c68 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/IFilteredNone.cs @@ -0,0 +1,8 @@ +namespace SolidStack.Core.Flow.Internal.Option +{ + public interface IFilteredNone : + IFilteredActionableVoid>, + IFilteredMappableNone + { + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/IFilteredPredicatedSome.cs b/src/SolidStack.Core.Flow/Internal/Option/IFilteredPredicatedSome.cs new file mode 100644 index 0000000..723f386 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/IFilteredPredicatedSome.cs @@ -0,0 +1,8 @@ +namespace SolidStack.Core.Flow.Internal.Option +{ + public interface IFilteredPredicatedSome : + IFilteredActionableContent>, + IFilteredMappablePredicatedSome + { + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/IFilteredSome.cs b/src/SolidStack.Core.Flow/Internal/Option/IFilteredSome.cs new file mode 100644 index 0000000..131d27f --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/IFilteredSome.cs @@ -0,0 +1,8 @@ +namespace SolidStack.Core.Flow.Internal.Option +{ + public interface IFilteredSome : + IFilteredActionableContent>, + IFilteredMappableSome + { + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/IMappableNone.cs b/src/SolidStack.Core.Flow/Internal/Option/IMappableNone.cs new file mode 100644 index 0000000..ea4f2a6 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/IMappableNone.cs @@ -0,0 +1,24 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Option +{ + public interface IMappableNone + { + /// + /// Filters the current flow to be able to define a mapping action for it that will be executed only when there is a value and that + /// value matches the given predicate. + /// + /// The predicate to match against the value. + /// The filtered flow containing the following possible mapping action to perform on the optional value. + /// If the given mapping action is null. + IFilteredMappableContent> When( + Func predicate); + + /// + /// Filters the current flow to be able to define a mapping action for it that will be executed only when there is a value. + /// + /// The filtered flow containing the following possible mapping action to perform on the optional value. + IFilteredMappableContent WhenSome(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/IMappablePredicatedSome.cs b/src/SolidStack.Core.Flow/Internal/Option/IMappablePredicatedSome.cs new file mode 100644 index 0000000..c23e31d --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/IMappablePredicatedSome.cs @@ -0,0 +1,25 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Option +{ + public interface IMappablePredicatedSome + { + /// + /// Filters the current flow to be able to define a mapping action for it that will be executed only when there is a value and that + /// value matches the given predicate. + /// + /// The predicate to match against the value. + /// The filtered flow containing the following possible mapping action to perform on the optional value. + /// If the given predicate is null. + + IFilteredMappableContent> When( + Func predicate); + + /// + /// Filters the current flow to be able to define a mapping action for it that will be executed only when there is a value. + /// + /// The filtered flow containing the following possible mapping action to perform on the optional value. + IFilteredMappableContent> WhenSome(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/IMappableSome.cs b/src/SolidStack.Core.Flow/Internal/Option/IMappableSome.cs new file mode 100644 index 0000000..039a70f --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/IMappableSome.cs @@ -0,0 +1,11 @@ +namespace SolidStack.Core.Flow.Internal.Option +{ + public interface IMappableSome + { + /// + /// Filters the current flow to be able to create actions that will be executed only when there is no value. + /// + /// The filtered flow containing the following possible actions that can be performed on the optional value. + IFilteredMappableVoid WhenNone(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/ResolvedActionableNone.cs b/src/SolidStack.Core.Flow/Internal/Option/ResolvedActionableNone.cs new file mode 100644 index 0000000..b2f6e31 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/ResolvedActionableNone.cs @@ -0,0 +1,26 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class ResolvedActionableNone : ActionableVoid>, + IActionableOption + { + public ResolvedActionableNone() + { + } + + public ResolvedActionableNone(Action firstAction) : + base(firstAction) + { + } + + public IFilteredActionableContent> When(Func predicate) => + SkipNextContentAction(); + + public IFilteredActionableVoid> WhenNone() => + AppendNextAction(); + + public IFilteredActionableContent> WhenSome() => + SkipNextContentAction(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/ResolvedActionableSome.cs b/src/SolidStack.Core.Flow/Internal/Option/ResolvedActionableSome.cs new file mode 100644 index 0000000..2214dd7 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/ResolvedActionableSome.cs @@ -0,0 +1,29 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class ResolvedActionableSome : ActionableContent, T>, + IActionableOption + { + public ResolvedActionableSome(T content) : + base(content) + { + } + + public ResolvedActionableSome(T content, Action firstAction) : + base(content, firstAction) + { + } + + public IFilteredActionableContent> When(Func predicate) => + predicate(Content) + ? AppendNextAction() + : SkipNextAction(); + + public IFilteredActionableVoid> WhenNone() => + SkipNextVoidAction(); + + public IFilteredActionableContent> WhenSome() => + AppendNextAction(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/ResolvedFilteredNone.cs b/src/SolidStack.Core.Flow/Internal/Option/ResolvedFilteredNone.cs new file mode 100644 index 0000000..93fc6a5 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/ResolvedFilteredNone.cs @@ -0,0 +1,23 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class ResolvedFilteredNone : + IFilteredNone + { + public IActionableOption Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + return new ResolvedActionableNone(action); + } + + public IMappableNone MapTo(Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + return new ResolvedMappableNone(mapping); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/ResolvedFilteredPredicatedSome.cs b/src/SolidStack.Core.Flow/Internal/Option/ResolvedFilteredPredicatedSome.cs new file mode 100644 index 0000000..769288d --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/ResolvedFilteredPredicatedSome.cs @@ -0,0 +1,38 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class ResolvedFilteredPredicatedSome : + IFilteredPredicatedSome + { + public ResolvedFilteredPredicatedSome(T content, Func predicate) + { + Content = content; + Predicate = predicate; + } + + private T Content { get; } + + private Func Predicate { get; } + + public IActionableOption Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + return Predicate(Content) + ? new ResolvedActionableSome(Content, action) + : new ResolvedActionableSome(Content); + } + + public IMappablePredicatedSome MapTo( + Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + return Predicate(Content) + ? new ResolvedMappablePredicatedSome(Content, mapping) + : new ResolvedMappablePredicatedSome(Content); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/ResolvedFilteredSome.cs b/src/SolidStack.Core.Flow/Internal/Option/ResolvedFilteredSome.cs new file mode 100644 index 0000000..9aace09 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/ResolvedFilteredSome.cs @@ -0,0 +1,28 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class ResolvedFilteredSome : + IFilteredSome + { + public ResolvedFilteredSome(T content) => + Content = content; + + private T Content { get; } + + public IActionableOption Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + return new ResolvedActionableSome(Content, action); + } + + public IMappableSome MapTo(Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + return new ResolvedMappableSome(Content, mapping); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/ResolvedMappableNone.cs b/src/SolidStack.Core.Flow/Internal/Option/ResolvedMappableNone.cs new file mode 100644 index 0000000..64a5da6 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/ResolvedMappableNone.cs @@ -0,0 +1,25 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class ResolvedMappableNone : MappableVoid< + ResolvedMappableNone, TMappingDestination>, + IMappableNone + { + public ResolvedMappableNone() + { + } + + public ResolvedMappableNone(Func firstMapping) : + base(firstMapping) + { + } + + public IFilteredMappableContent> When( + Func predicate) => + SkipNextContentMapping(); + + public IFilteredMappableContent WhenSome() => + SkipLastContentMapping(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/ResolvedMappablePredicatedSome.cs b/src/SolidStack.Core.Flow/Internal/Option/ResolvedMappablePredicatedSome.cs new file mode 100644 index 0000000..8c3b0e6 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/ResolvedMappablePredicatedSome.cs @@ -0,0 +1,32 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class ResolvedMappablePredicatedSome : MappableContent< + ResolvedMappablePredicatedSome, T, TMappingDestination>, + IMappablePredicatedSome, + IMappableSome + { + public ResolvedMappablePredicatedSome(T content) : + base(content) + { + } + + public ResolvedMappablePredicatedSome(T content, Func firstMapping) : + base(content, firstMapping) + { + } + + public IFilteredMappableContent> When( + Func predicate) => + predicate(Content) + ? TryUseNextMapping() + : SkipNextMapping(); + + public IFilteredMappableContent> WhenSome() => + TryUseNextMapping(); + + public IFilteredMappableVoid WhenNone() => + SkipLastVoidMapping(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/ResolvedMappableSome.cs b/src/SolidStack.Core.Flow/Internal/Option/ResolvedMappableSome.cs new file mode 100644 index 0000000..0655ae1 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/ResolvedMappableSome.cs @@ -0,0 +1,22 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class ResolvedMappableSome : MappableContent< + ResolvedMappableSome, T, TMappingDestination>, + IMappableSome + { + public ResolvedMappableSome(T content) : + base(content) + { + } + + public ResolvedMappableSome(T content, Func firstMapping) : + base(content, firstMapping) + { + } + + public IFilteredMappableVoid WhenNone() => + SkipLastVoidMapping(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/UnresolvedFilteredNone.cs b/src/SolidStack.Core.Flow/Internal/Option/UnresolvedFilteredNone.cs new file mode 100644 index 0000000..fa7431f --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/UnresolvedFilteredNone.cs @@ -0,0 +1,28 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class UnresolvedFilteredNone : + IFilteredNone + { + public UnresolvedFilteredNone(T content) => + Content = content; + + private T Content { get; } + + public IActionableOption Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + return new ResolvedActionableSome(Content); + } + + public IMappableNone MapTo(Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + return new UnresolvedMappableNone(Content); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/UnresolvedFilteredPredicatedSome.cs b/src/SolidStack.Core.Flow/Internal/Option/UnresolvedFilteredPredicatedSome.cs new file mode 100644 index 0000000..41e6bdb --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/UnresolvedFilteredPredicatedSome.cs @@ -0,0 +1,24 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class UnresolvedFilteredPredicatedSome : + IFilteredPredicatedSome + { + public IActionableOption Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + return new ResolvedActionableNone(); + } + + public IMappablePredicatedSome MapTo( + Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + return new UnresolvedMappablePredicatedSome(); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/UnresolvedFilteredSome.cs b/src/SolidStack.Core.Flow/Internal/Option/UnresolvedFilteredSome.cs new file mode 100644 index 0000000..6769dfb --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/UnresolvedFilteredSome.cs @@ -0,0 +1,23 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class UnresolvedFilteredSome : + IFilteredSome + { + public IActionableOption Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + return new ResolvedActionableNone(); + } + + public IMappableSome MapTo(Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + return new UnresolvedMappableSome(); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/UnresolvedMappableNone.cs b/src/SolidStack.Core.Flow/Internal/Option/UnresolvedMappableNone.cs new file mode 100644 index 0000000..a59d7c6 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/UnresolvedMappableNone.cs @@ -0,0 +1,28 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class UnresolvedMappableNone : MappableContent< + UnresolvedMappableNone, T, TMappingDestination>, + IMappableNone + { + public UnresolvedMappableNone(T content) : + base(content) + { + } + + public UnresolvedMappableNone(T content, Func firstMapping) : + base(content, firstMapping) + { + } + + public IFilteredMappableContent> When( + Func predicate) => + predicate(Content) + ? TryUseNextMapping() + : SkipNextMapping(); + + public IFilteredMappableContent WhenSome() => + TryUseLastMapping(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/UnresolvedMappablePredicatedSome.cs b/src/SolidStack.Core.Flow/Internal/Option/UnresolvedMappablePredicatedSome.cs new file mode 100644 index 0000000..ac08cce --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/UnresolvedMappablePredicatedSome.cs @@ -0,0 +1,20 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class UnresolvedMappablePredicatedSome : MappableVoid< + UnresolvedMappablePredicatedSome, TMappingDestination>, + IMappablePredicatedSome, + IMappableSome + { + public IFilteredMappableContent> When( + Func predicate) => + SkipNextContentMapping(); + + public IFilteredMappableContent> WhenSome() => + SkipNextContentMapping(); + + public IFilteredMappableVoid WhenNone() => + TryUseLastMapping(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Option/UnresolvedMappableSome.cs b/src/SolidStack.Core.Flow/Internal/Option/UnresolvedMappableSome.cs new file mode 100644 index 0000000..b96a6c5 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Option/UnresolvedMappableSome.cs @@ -0,0 +1,10 @@ +namespace SolidStack.Core.Flow.Internal.Option +{ + internal class UnresolvedMappableSome : MappableVoid< + UnresolvedMappableSome, TMappingDestination>, + IMappableSome + { + public IFilteredMappableVoid WhenNone() => + TryUseLastMapping(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/IActionableResult.cs b/src/SolidStack.Core.Flow/Internal/Result/IActionableResult.cs new file mode 100644 index 0000000..5ac6b37 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/IActionableResult.cs @@ -0,0 +1,27 @@ +namespace SolidStack.Core.Flow.Internal.Result +{ + public interface IActionableResult : + IActionable + { + /// + /// Filters the current flow to be able to create actions that will be executed only when the result is an error and + /// that error is assignable to the given specific error type. + /// + /// The type of the specific error matched against the actual error. + /// The filtered flow containing the following possible actions that can be performed on the result value. + IFilteredActionableContent> WhenError() + where TSpecificError : TError; + + /// + /// Filters the current flow to be able to create actions that will be executed only when the result is an error. + /// + /// The filtered flow containing the following possible actions that can be performed on the result value. + IFilteredActionableContent> WhenError(); + + /// + /// Filters the current flow to be able to create actions that will be executed only when the result is a success. + /// + /// The filtered flow containing the following possible actions that can be performed on the result value. + IFilteredActionableContent> WhenSuccess(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/IFilteredError.cs b/src/SolidStack.Core.Flow/Internal/Result/IFilteredError.cs new file mode 100644 index 0000000..d9db3ff --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/IFilteredError.cs @@ -0,0 +1,8 @@ +namespace SolidStack.Core.Flow.Internal.Result +{ + public interface IFilteredError : + IFilteredActionableContent>, + IFilteredMappableError + { + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/IFilteredMappableError.cs b/src/SolidStack.Core.Flow/Internal/Result/IFilteredMappableError.cs new file mode 100644 index 0000000..50eb316 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/IFilteredMappableError.cs @@ -0,0 +1,18 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Result +{ + public interface IFilteredMappableError + { + /// + /// Adds a mapping action to the current filtered flow that will be executed further if the initial result is an error. + /// + /// The destination type to create. + /// The mapping action to use. + /// The updated flow. + /// If the given mapping action is null. + IMappableError MapTo( + Func mapping); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/IFilteredMappableSpecificError.cs b/src/SolidStack.Core.Flow/Internal/Result/IFilteredMappableSpecificError.cs new file mode 100644 index 0000000..049661d --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/IFilteredMappableSpecificError.cs @@ -0,0 +1,20 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Result +{ + public interface IFilteredMappableSpecificError + where TSpecificError : TError + { + /// + /// Adds a mapping action to the current filtered flow that will be executed further if the initial result is an error + /// and that error is assignable to the given specific error type. + /// + /// The destination type to create. + /// The mapping action to use. + /// The updated flow. + /// If the given mapping action is null. + IMappableSpecificError MapTo( + Func mapping); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/IFilteredMappableSuccess.cs b/src/SolidStack.Core.Flow/Internal/Result/IFilteredMappableSuccess.cs new file mode 100644 index 0000000..a95e484 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/IFilteredMappableSuccess.cs @@ -0,0 +1,18 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Result +{ + public interface IFilteredMappableSuccess + { + /// + /// Adds a mapping action to the current filtered flow that will be executed further if the initial result is a success. + /// + /// The destination type to create. + /// The mapping action to use. + /// The updated flow. + /// If the given mapping action is null. + IMappableSuccess MapTo( + Func mapping); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/IFilteredSpecificError.cs b/src/SolidStack.Core.Flow/Internal/Result/IFilteredSpecificError.cs new file mode 100644 index 0000000..05617f2 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/IFilteredSpecificError.cs @@ -0,0 +1,9 @@ +namespace SolidStack.Core.Flow.Internal.Result +{ + public interface IFilteredSpecificError : + IFilteredActionableContent>, + IFilteredMappableSpecificError + where TSpecificError : TError + { + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/IFilteredSuccess.cs b/src/SolidStack.Core.Flow/Internal/Result/IFilteredSuccess.cs new file mode 100644 index 0000000..2ba13a7 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/IFilteredSuccess.cs @@ -0,0 +1,8 @@ +namespace SolidStack.Core.Flow.Internal.Result +{ + public interface IFilteredSuccess : + IFilteredActionableContent>, + IFilteredMappableSuccess + { + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/IMappableError.cs b/src/SolidStack.Core.Flow/Internal/Result/IMappableError.cs new file mode 100644 index 0000000..068d355 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/IMappableError.cs @@ -0,0 +1,11 @@ +namespace SolidStack.Core.Flow.Internal.Result +{ + public interface IMappableError + { + /// + /// Filters the current flow to be able to define a mapping action for it that will be executed only when the result is an error. + /// + /// The filtered flow containing the following possible mapping action to perform on the result value. + IFilteredMappableContent WhenSuccess(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/IMappableSpecificError.cs b/src/SolidStack.Core.Flow/Internal/Result/IMappableSpecificError.cs new file mode 100644 index 0000000..53f9ad4 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/IMappableSpecificError.cs @@ -0,0 +1,24 @@ +namespace SolidStack.Core.Flow.Internal.Result +{ + public interface IMappableSpecificError + { + /// + /// Filters the current flow to be able to define a mapping action for it that will be executed only when the result is + /// an error and that error is assignable to the given specific error type. + /// + /// The filtered flow containing the following possible mapping action to perform on the result value. + IFilteredMappableContent< + TSpecificError, TMappingDestination, + IMappableSpecificError> + WhenError() + where TSpecificError : TError; + + /// + /// Filters the current flow to be able to define a mapping action for it that will be executed only when the result is + /// an error. + /// + /// The filtered flow containing the following possible mapping action to perform on the result value. + IFilteredMappableContent> + WhenError(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/IMappableSuccess.cs b/src/SolidStack.Core.Flow/Internal/Result/IMappableSuccess.cs new file mode 100644 index 0000000..353e826 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/IMappableSuccess.cs @@ -0,0 +1,18 @@ +namespace SolidStack.Core.Flow.Internal.Result +{ + public interface IMappableSuccess + { + /// + /// Filters the current flow to be able to define a mapping action for it that will be executed only when the result is + /// an error. + /// + /// The filtered flow containing the following possible mapping action to perform on the result value. + IFilteredMappableContent< + TSpecificError, TMappingDestination, + IMappableSuccess> + WhenError() + where TSpecificError : TError; + + IFilteredMappableContent WhenError(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/ResolvedActionableError.cs b/src/SolidStack.Core.Flow/Internal/Result/ResolvedActionableError.cs new file mode 100644 index 0000000..2d88cdd --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/ResolvedActionableError.cs @@ -0,0 +1,31 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class ResolvedActionableError : + ActionableContent, TError>, + IActionableResult + { + public ResolvedActionableError(TError content) : + base(content) + { + } + + public ResolvedActionableError(TError content, Action firstAction) : + base(content, firstAction) + { + } + + public IFilteredActionableContent> WhenError< + TSpecificError>() where TSpecificError : TError => + Content is TSpecificError + ? AppendNextAction() + : SkipNextAction(); + + public IFilteredActionableContent> WhenError() => + AppendNextAction(); + + public IFilteredActionableContent> WhenSuccess() => + SkipNextAction(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/ResolvedActionableSuccess.cs b/src/SolidStack.Core.Flow/Internal/Result/ResolvedActionableSuccess.cs new file mode 100644 index 0000000..edfe344 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/ResolvedActionableSuccess.cs @@ -0,0 +1,29 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class ResolvedActionableSuccess : + ActionableContent, TSuccess>, + IActionableResult + { + public ResolvedActionableSuccess(TSuccess content) : + base(content) + { + } + + public ResolvedActionableSuccess(TSuccess content, Action firstAction) : + base(content, firstAction) + { + } + + public IFilteredActionableContent> WhenError< + TSpecificError>() where TSpecificError : TError => + SkipNextAction(); + + public IFilteredActionableContent> WhenError() => + SkipNextAction(); + + public IFilteredActionableContent> WhenSuccess() => + AppendNextAction(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/ResolvedFilteredError.cs b/src/SolidStack.Core.Flow/Internal/Result/ResolvedFilteredError.cs new file mode 100644 index 0000000..ebe56e5 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/ResolvedFilteredError.cs @@ -0,0 +1,29 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class ResolvedFilteredError : + IFilteredError + { + public ResolvedFilteredError(TError error) => + Error = error; + + private TError Error { get; } + + public IActionableResult Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + return new ResolvedActionableError(Error, action); + } + + public IMappableError MapTo( + Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + return new ResolvedMappableError(Error, mapping); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/ResolvedFilteredSpecificError.cs b/src/SolidStack.Core.Flow/Internal/Result/ResolvedFilteredSpecificError.cs new file mode 100644 index 0000000..ea89338 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/ResolvedFilteredSpecificError.cs @@ -0,0 +1,36 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class ResolvedFilteredSpecificError : + IFilteredSpecificError + where TSpecificError : TError + { + public ResolvedFilteredSpecificError(TError error) => + Error = error; + + private TError Error { get; } + + public IActionableResult Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + return Error is TSpecificError + ? new ResolvedActionableError( + Error, error => action((TSpecificError) error)) + : new ResolvedActionableError(Error); + } + + public IMappableSpecificError MapTo( + Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + return Error is TSpecificError + ? new ResolvedMappableSpecificError( + Error, error => mapping((TSpecificError) error)) + : new ResolvedMappableSpecificError(Error); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/ResolvedFilteredSuccess.cs b/src/SolidStack.Core.Flow/Internal/Result/ResolvedFilteredSuccess.cs new file mode 100644 index 0000000..683401f --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/ResolvedFilteredSuccess.cs @@ -0,0 +1,29 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class ResolvedFilteredSuccess : + IFilteredSuccess + { + public ResolvedFilteredSuccess(TSuccess success) => + Success = success; + + private TSuccess Success { get; } + + public IActionableResult Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + return new ResolvedActionableSuccess(Success, action); + } + + public IMappableSuccess MapTo( + Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + return new ResolvedMappableSuccess(Success, mapping); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/ResolvedMappableError.cs b/src/SolidStack.Core.Flow/Internal/Result/ResolvedMappableError.cs new file mode 100644 index 0000000..046673c --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/ResolvedMappableError.cs @@ -0,0 +1,22 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class ResolvedMappableError : MappableContent< + ResolvedMappableError, TError, TMappingDestination>, + IMappableError + { + public ResolvedMappableError(TError content) : + base(content) + { + } + + public ResolvedMappableError(TError content, Func firstMapping) : + base(content, firstMapping) + { + } + + public IFilteredMappableContent WhenSuccess() => + SkipLastMapping(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/ResolvedMappableSpecificError.cs b/src/SolidStack.Core.Flow/Internal/Result/ResolvedMappableSpecificError.cs new file mode 100644 index 0000000..90080c8 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/ResolvedMappableSpecificError.cs @@ -0,0 +1,38 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class ResolvedMappableSpecificError : MappableContent< + ResolvedMappableSpecificError, TError, TMappingDestination>, + IMappableSpecificError, + IMappableError + { + public ResolvedMappableSpecificError(TError content) : + base(content) + { + } + + public ResolvedMappableSpecificError(TError content, Func firstMapping) : + base(content, firstMapping) + { + } + + public IFilteredMappableContent WhenSuccess() => + SkipLastMapping(); + + public IFilteredMappableContent< + TSpecificError, TMappingDestination, + IMappableSpecificError> + WhenError() + where TSpecificError : TError => + Content is TSpecificError + ? TryUseNextMapping() + : SkipNextMapping(); + + public IFilteredMappableContent< + TError, TMappingDestination, + IMappableError> + WhenError() => + TryUseNextMapping(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/ResolvedMappableSuccess.cs b/src/SolidStack.Core.Flow/Internal/Result/ResolvedMappableSuccess.cs new file mode 100644 index 0000000..49322c9 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/ResolvedMappableSuccess.cs @@ -0,0 +1,29 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class ResolvedMappableSuccess : MappableContent< + ResolvedMappableSuccess, TSuccess, TMappingDestination>, + IMappableSuccess + { + public ResolvedMappableSuccess(TSuccess content) : + base(content) + { + } + + public ResolvedMappableSuccess(TSuccess content, Func firstMapping) : + base(content, firstMapping) + { + } + + public IFilteredMappableContent< + TSpecificError, TMappingDestination, + IMappableSuccess> + WhenError() + where TSpecificError : TError => + SkipNextMapping(); + + public IFilteredMappableContent WhenError() => + SkipLastMapping(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/UnresolvedFilteredError.cs b/src/SolidStack.Core.Flow/Internal/Result/UnresolvedFilteredError.cs new file mode 100644 index 0000000..1fefbb8 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/UnresolvedFilteredError.cs @@ -0,0 +1,29 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class UnresolvedFilteredError : + IFilteredError + { + public UnresolvedFilteredError(TSuccess success) => + Success = success; + + private TSuccess Success { get; } + + public IActionableResult Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + return new ResolvedActionableSuccess(Success); + } + + public IMappableError MapTo( + Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + return new UnresolvedMappableError(Success); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/UnresolvedFilteredSpecificError.cs b/src/SolidStack.Core.Flow/Internal/Result/UnresolvedFilteredSpecificError.cs new file mode 100644 index 0000000..a64ed2e --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/UnresolvedFilteredSpecificError.cs @@ -0,0 +1,30 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class UnresolvedFilteredSpecificError : + IFilteredSpecificError + where TSpecificError : TError + { + public UnresolvedFilteredSpecificError(TSuccess success) => + Success = success; + + private TSuccess Success { get; } + + public IActionableResult Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + return new ResolvedActionableSuccess(Success); + } + + public IMappableSpecificError MapTo( + Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + return new UnresolvedMappableSpecificError(Success); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/UnresolvedFilteredSuccess.cs b/src/SolidStack.Core.Flow/Internal/Result/UnresolvedFilteredSuccess.cs new file mode 100644 index 0000000..3288a8e --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/UnresolvedFilteredSuccess.cs @@ -0,0 +1,29 @@ +using System; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class UnresolvedFilteredSuccess : + IFilteredSuccess + { + public UnresolvedFilteredSuccess(TError error) => + Error = error; + + private TError Error { get; } + + public IActionableResult Do(Action action) + { + Guard.RequiresNonNull(action, nameof(action)); + + return new ResolvedActionableError(Error); + } + + public IMappableSuccess MapTo( + Func mapping) + { + Guard.RequiresNonNull(mapping, nameof(mapping)); + + return new UnresolvedMappableSuccess(Error); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/UnresolvedMappableError.cs b/src/SolidStack.Core.Flow/Internal/Result/UnresolvedMappableError.cs new file mode 100644 index 0000000..9313212 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/UnresolvedMappableError.cs @@ -0,0 +1,22 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class UnresolvedMappableError : MappableContent< + ResolvedMappableSuccess, TSuccess, TMappingDestination>, + IMappableError + { + public UnresolvedMappableError(TSuccess content) : + base(content) + { + } + + public UnresolvedMappableError(TSuccess content, Func firstMapping) : + base(content, firstMapping) + { + } + + public IFilteredMappableContent WhenSuccess() => + TryUseLastMapping(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/UnresolvedMappableSpecificError.cs b/src/SolidStack.Core.Flow/Internal/Result/UnresolvedMappableSpecificError.cs new file mode 100644 index 0000000..35c18e6 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/UnresolvedMappableSpecificError.cs @@ -0,0 +1,34 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class UnresolvedMappableSpecificError : MappableContent< + UnresolvedMappableSpecificError, TSuccess, TMappingDestination>, + IMappableSpecificError, + IMappableError + { + public UnresolvedMappableSpecificError(TSuccess content) : + base(content) + { + } + + public UnresolvedMappableSpecificError(TSuccess content, Func firstMapping) : + base(content, firstMapping) + { + } + + public IFilteredMappableContent WhenSuccess() => + TryUseLastMapping(); + + public IFilteredMappableContent< + TSpecificError, TMappingDestination, + IMappableSpecificError> + WhenError() + where TSpecificError : TError => + SkipNextMapping(); + + public IFilteredMappableContent> WhenError() => + SkipNextMapping(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Internal/Result/UnresolvedMappableSuccess.cs b/src/SolidStack.Core.Flow/Internal/Result/UnresolvedMappableSuccess.cs new file mode 100644 index 0000000..eaf7371 --- /dev/null +++ b/src/SolidStack.Core.Flow/Internal/Result/UnresolvedMappableSuccess.cs @@ -0,0 +1,31 @@ +using System; + +namespace SolidStack.Core.Flow.Internal.Result +{ + internal class UnresolvedMappableSuccess : MappableContent< + UnresolvedMappableSuccess, TError, TMappingDestination>, + IMappableSuccess + { + public UnresolvedMappableSuccess(TError content) : + base(content) + { + } + + public UnresolvedMappableSuccess(TError content, Func firstMapping) : + base(content, firstMapping) + { + } + + public IFilteredMappableContent< + TSpecificError, TMappingDestination, + IMappableSuccess> + WhenError() + where TSpecificError : TError => + Content is TSpecificError + ? TryUseNextMapping() + : SkipNextMapping(); + + public IFilteredMappableContent WhenError() => + TryUseLastMapping(); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Option.cs b/src/SolidStack.Core.Flow/Option.cs new file mode 100644 index 0000000..a14680a --- /dev/null +++ b/src/SolidStack.Core.Flow/Option.cs @@ -0,0 +1,31 @@ +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow +{ + public static class Option + { + /// + /// Creates an option using an existing value; if the value is null, the option is created with no value. + /// + /// + /// The created option. + public static IOption From(T optionnal) => + Option.From(optionnal); + + /// + /// Creates an option that doesn't contain a value. + /// + /// The created option. + public static IOption None() => + Option.None(); + + /// + /// Creates an option that contain a value. + /// + /// + /// The created option. + /// If the given value is null. + public static IOption Some(T value) => + Option.Some(value); + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/OptionAdapters.cs b/src/SolidStack.Core.Flow/OptionAdapters.cs new file mode 100644 index 0000000..8ddc21a --- /dev/null +++ b/src/SolidStack.Core.Flow/OptionAdapters.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow +{ + public static class OptionAdapters + { + /// + /// Returns a empty sequence if there is no value or a sequence of one item if there is a value. + /// + /// + /// The returned sequence will never contain more than one element. + /// + /// The sequence of zero or one item. + /// If the given option is null. + public static IEnumerable AsEnumerable(this IOption option) + { + Guard.RequiresNonNull(option, nameof(option)); + + return option + .WhenSome() + .MapTo(item => new[] {item}) + .WhenNone() + .MapTo(() => new T[0]) + .Map(); + } + + /// + /// Returns an option containing the first element of a sequence, or an empty option if the sequence contains no + /// elements. + /// + /// The type of the sequence elements. + /// The sequence to search in. + /// An option containing the first element or an empty option. + /// If the given sequence is null. + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] + public static IOption TryFirst(this IEnumerable sequence) + { + Guard.RequiresNonNull(sequence, nameof(sequence)); + + if (sequence is IList list) + { + if (list.Count > 0) + return Option.Some(list[0]); + } + else + { + using (var enumerator = sequence.GetEnumerator()) + { + if (enumerator.MoveNext()) + return Option.Some(enumerator.Current); + } + } + + return Option.None(); + } + + /// + /// Returns an option containing the first element of the sequence that satisfies a condition or a an empty option if + /// no such element is found. + /// + /// The type of the sequence elements. + /// The sequence to search in. + /// A function to test each element for a condition. + /// An option containing the first element found or an empty option. + /// If the given sequence is null. + /// If the given predicate is null. + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] + public static IOption TryFirst(this IEnumerable sequence, Func predicate) + { + Guard.RequiresNonNull(sequence, nameof(sequence)); + Guard.RequiresNonNull(predicate, nameof(predicate)); + + // ReSharper disable once LoopCanBePartlyConvertedToQuery + foreach (var element in sequence) + if (predicate(element)) + return Option.Some(element); + + return Option.None(); + } + + /// + /// Gets the value associated with the specified key or an empty option if the value can't be found. + /// + /// The type of the key. + /// The type of the value. + /// The dictionary to search in. + /// The key whose value to get. + /// + /// An option with some value if the dictionary contains an element with the specified key; otherwise, an empty + /// option. + /// + /// If the given dictionary in null. + /// If the given key in null. + public static IOption TryGetValue(this IDictionary dictionary, TKey key) + { + Guard.RequiresNonNull(dictionary, nameof(dictionary)); + Guard.RequiresNonNull(key, nameof(key)); + + return dictionary.TryGetValue(key, out var value) + ? Option.Some(value) + : Option.None(); + } + + /// + /// Returns an option containing the only element of a sequence, or an empty option if the sequence contains no + /// elements. + /// + /// The type of the sequence elements. + /// The sequence to search in. + /// An option containing the first element or an empty option. + /// If the given sequence is null. + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] + public static IOption TrySingle(this IEnumerable sequence) + { + Guard.RequiresNonNull(sequence, nameof(sequence)); + + if (sequence is IList list) + switch (list.Count) + { + case 0: + return Option.None(); + case 1: + return Option.Some(list[0]); + default: + return Option.None(); + } + + using (var enumerator = sequence.GetEnumerator()) + { + if (!enumerator.MoveNext()) + return Option.None(); + + var result = enumerator.Current; + + if (!enumerator.MoveNext()) + return Option.Some(result); + } + + return Option.None(); + } + + /// + /// Returns an option containing the only element of a sequence, or an empty option if the sequence contains no + /// elements. + /// + /// The type of the sequence elements. + /// The sequence to search in. + /// A function to test each element for a condition. + /// An option containing the first element or an empty option. + /// If the given sequence is null. + /// If the given predicate is null. + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] + public static IOption TrySingle(this IEnumerable sequence, Func predicate) + { + Guard.RequiresNonNull(sequence, nameof(sequence)); + Guard.RequiresNonNull(predicate, nameof(predicate)); + + var result = Option.None(); + long count = 0; + + // ReSharper disable once LoopCanBePartlyConvertedToQuery + foreach (var element in sequence) + if (predicate(element)) + { + result = Option.Some(element); + checked + { + count++; + } + } + + switch (count) + { + case 0: + return Option.None(); + case 1: + return result; + default: + return Option.None(); + } + } + + /// + /// Keeps only the options that contain a value. + /// + /// The type of the sequence elements. + /// The sequence of options to filter. + /// A new sequence with no more optional elements. + /// If the given sequence is null. + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] + public static IEnumerable WhereSome(this IEnumerable> sequence) + { + Guard.RequiresNonNull(sequence, nameof(sequence)); + + return sequence.SelectMany(option => option.AsEnumerable()).ToList(); + } + + /// + /// Projects each element of a sequence into a optional form and then keeps only the options that contain a value. + /// + /// The type of the sequence elements. + /// The destination type of the sequence elements. + /// The sequence of values to invoke a transform function on. + /// The transform function to apply to each element. + /// A sequence containing only the elements that have returned an option with some value after being transformed. + /// If the given sequence is null. + /// If the given predicate is null. + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] + public static IEnumerable WhereSome + (this IEnumerable sequence, Func> predicate) + { + Guard.RequiresNonNull(sequence, nameof(sequence)); + Guard.RequiresNonNull(predicate, nameof(predicate)); + + return sequence.Select(predicate).WhereSome(); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Option`.cs b/src/SolidStack.Core.Flow/Option`.cs new file mode 100644 index 0000000..3d966a8 --- /dev/null +++ b/src/SolidStack.Core.Flow/Option`.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SolidStack.Core.Flow.Internal.Option; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow +{ + /// + public class Option : + IOption + { + private Option(IEnumerable content) => + Content = content; + + private IEnumerable Content { get; } + + /// + public IFilteredPredicatedSome When(Func predicate) + { + Guard.RequiresNonNull(predicate, nameof(predicate)); + + return + Content + .Select>(item => + new ResolvedFilteredPredicatedSome(item, predicate)) + .DefaultIfEmpty(new UnresolvedFilteredPredicatedSome()) + .Single(); + } + + /// + public IFilteredNone WhenNone() => + Content + .Select>(item => new UnresolvedFilteredNone(item)) + .DefaultIfEmpty(new ResolvedFilteredNone()) + .Single(); + + /// + public IFilteredSome WhenSome() => + Content + .Select>(item => new ResolvedFilteredSome(item)) + .DefaultIfEmpty(new UnresolvedFilteredSome()) + .Single(); + + /// + /// Creates an option using an existing value; if the value is null, the option is created with no value. + /// + /// + /// The created option. + public static IOption From(T optionnal) => + optionnal != null + ? Some(optionnal) + : None(); + + /// + /// Creates an option that doesn't contain a value. + /// + /// The created option. + public static IOption None() => + new Option(new T[0]); + + /// + /// Creates an option that contain a value. + /// + /// + /// The created option. + /// If the given value is null. + public static IOption Some(T value) + { + Guard.RequiresNonNull(value, nameof(value)); + + return new Option(new[] {value}); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/Result.cs b/src/SolidStack.Core.Flow/Result.cs new file mode 100644 index 0000000..bd629b2 --- /dev/null +++ b/src/SolidStack.Core.Flow/Result.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Linq; +using SolidStack.Core.Flow.Internal.Result; +using SolidStack.Core.Guards; + +namespace SolidStack.Core.Flow +{ + public class Result : + IResult + { + private Result(IEnumerable errorContent, IEnumerable successContent) + { + ErrorContent = errorContent; + SuccessContent = successContent; + } + + private IEnumerable ErrorContent { get; } + + private IEnumerable SuccessContent { get; } + + public IFilteredSpecificError WhenError() + where TSpecificError : TError => + ErrorContent + .Select>( + item => new ResolvedFilteredSpecificError(item)) + .DefaultIfEmpty( + new UnresolvedFilteredSpecificError( + SuccessContent.SingleOrDefault())) + .Single(); + + public IFilteredError WhenError() => + ErrorContent + .Select>( + item => new ResolvedFilteredError(item)) + .DefaultIfEmpty(new UnresolvedFilteredError(SuccessContent.SingleOrDefault())) + .Single(); + + public IFilteredSuccess WhenSuccess() => + SuccessContent + .Select>( + item => new ResolvedFilteredSuccess(item)) + .DefaultIfEmpty(new UnresolvedFilteredSuccess(ErrorContent.SingleOrDefault())) + .Single(); + + public static IResult Error(TError error) + { + Guard.RequiresNonNull(error, nameof(error)); + + return new Result(new[] {error}, new TSuccess[0]); + } + + public static IResult Success(TSuccess success) + { + Guard.RequiresNonNull(success, nameof(success)); + + return new Result(new TError[0], new[] {success}); + } + } +} \ No newline at end of file diff --git a/src/SolidStack.Core.Flow/SolidStack.Core.Flow.csproj b/src/SolidStack.Core.Flow/SolidStack.Core.Flow.csproj new file mode 100644 index 0000000..e75229a --- /dev/null +++ b/src/SolidStack.Core.Flow/SolidStack.Core.Flow.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/src/SolidStack.sln b/src/SolidStack.sln index ca323ae..7cdc2bf 100644 --- a/src/SolidStack.sln +++ b/src/SolidStack.sln @@ -7,7 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SolidStack.Core.Guards", "S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SolidStack.Core.Guards.Tests", "SolidStack.Core.Guards.Tests\SolidStack.Core.Guards.Tests.csproj", "{3F4308F7-20C6-4E5E-BB94-8681BEEF3E3A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolidStack.Testing.Xunit", "SolidStack.Testing.Xunit\SolidStack.Testing.Xunit.csproj", "{5AE73D94-930C-4AA3-AA1D-704B9B28FB5C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SolidStack.Testing.Xunit", "SolidStack.Testing.Xunit\SolidStack.Testing.Xunit.csproj", "{5AE73D94-930C-4AA3-AA1D-704B9B28FB5C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SolidStack.Core.Flow", "SolidStack.Core.Flow\SolidStack.Core.Flow.csproj", "{193DDAA2-AD28-4892-B6D9-C155EA6BA698}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolidStack.Core.Flow.Tests", "SolidStack.Core.Flow.Tests\SolidStack.Core.Flow.Tests.csproj", "{C0E17594-BC69-4D31-ABD8-E74773D2CAD1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,6 +31,14 @@ Global {5AE73D94-930C-4AA3-AA1D-704B9B28FB5C}.Debug|Any CPU.Build.0 = Debug|Any CPU {5AE73D94-930C-4AA3-AA1D-704B9B28FB5C}.Release|Any CPU.ActiveCfg = Release|Any CPU {5AE73D94-930C-4AA3-AA1D-704B9B28FB5C}.Release|Any CPU.Build.0 = Release|Any CPU + {193DDAA2-AD28-4892-B6D9-C155EA6BA698}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {193DDAA2-AD28-4892-B6D9-C155EA6BA698}.Debug|Any CPU.Build.0 = Debug|Any CPU + {193DDAA2-AD28-4892-B6D9-C155EA6BA698}.Release|Any CPU.ActiveCfg = Release|Any CPU + {193DDAA2-AD28-4892-B6D9-C155EA6BA698}.Release|Any CPU.Build.0 = Release|Any CPU + {C0E17594-BC69-4D31-ABD8-E74773D2CAD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0E17594-BC69-4D31-ABD8-E74773D2CAD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0E17594-BC69-4D31-ABD8-E74773D2CAD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0E17594-BC69-4D31-ABD8-E74773D2CAD1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 0aa2f5dd8b4aa777b044e5377b7115cfec02306c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20G=C3=A9linas?= Date: Tue, 22 May 2018 22:27:24 -0400 Subject: [PATCH 2/9] Improve guard documentation --- .../GuardTests.cs | 1 - src/SolidStack.Core.Guards/Guard.cs | 60 +++++++++---------- .../{Internal => }/GuardClauseException.cs | 2 +- 3 files changed, 31 insertions(+), 32 deletions(-) rename src/SolidStack.Core.Guards/{Internal => }/GuardClauseException.cs (91%) diff --git a/src/SolidStack.Core.Guards.Tests/GuardTests.cs b/src/SolidStack.Core.Guards.Tests/GuardTests.cs index 0d21547..5f1dcfa 100644 --- a/src/SolidStack.Core.Guards.Tests/GuardTests.cs +++ b/src/SolidStack.Core.Guards.Tests/GuardTests.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using FluentAssertions; -using SolidStack.Core.Guards.Internal; using SolidStack.Testing.Xunit; using Xunit; diff --git a/src/SolidStack.Core.Guards/Guard.cs b/src/SolidStack.Core.Guards/Guard.cs index 310d811..6b900f4 100644 --- a/src/SolidStack.Core.Guards/Guard.cs +++ b/src/SolidStack.Core.Guards/Guard.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using SolidStack.Core.Guards.Internal; namespace SolidStack.Core.Guards { @@ -19,28 +18,29 @@ namespace SolidStack.Core.Guards public static class Guard { /// - /// Checks for a method post-condition; if the condition is false, displays an error message. + /// Checks for a method post-condition; if the condition is false, throws an exception that should never be catch. /// /// - /// The check will be performed in trigger mode only to avoid affecting performance. + /// Run in "debug" mode only to not affect performance. /// /// The condition to validate. /// The error message to display. + /// If the specified condition is not satisfied. [Conditional("DEBUG")] public static void Ensures(Func predicate, string message) => Assert(predicate, message); /// - /// Checks for a method post-condition against all items in a sequence; if any condition is false, displays an error - /// message. + /// Checks for a method post-condition against all items in a sequence; if any condition is false, throws an exception that should never be catch. /// /// - /// The check will be performed in "release" mode only to avoid affecting performance. + /// Run in "debug" mode only to not affect performance. /// /// The type of the sequence items. /// The sequence to check. /// The condition to validate. /// The error message to display. + /// If one or more items of the sequence doesn't satisfy the specified condition. [Conditional("DEBUG")] public static void EnsuresAll( IEnumerable sequence, @@ -48,16 +48,16 @@ public static void EnsuresAll( Assert(() => sequence.All(predicate), message); /// - /// Checks for a method post-condition against all items in a sequence; if all conditions are false, displays an error - /// message. + /// Checks for a method post-condition against all items in a sequence; if all conditions are false, throws an exception that should never be catch. /// /// - /// The check will be performed in "release" mode only to avoid affecting performance. + /// Run in "debug" mode only to not affect performance. /// /// The type of the sequence items. /// The sequence to check. /// The condition to validate. /// The error message to display. + /// If all items in the sequence doesn't satisfy the specified condition. [Conditional("DEBUG")] public static void EnsuresAny( IEnumerable sequence, @@ -65,72 +65,70 @@ public static void EnsuresAny( Ensures(() => sequence.Any(predicate), message); /// - /// Checks if a value is null as a method post-condition; if the value is null, displays an error message. + /// Checks if a value is null as a method post-condition; if the value is null, throws an exception that should never be catch. /// /// - /// The check will be performed in "release" mode only to avoid affecting performance. + /// Run in "debug" mode only to not affect performance. /// /// The type of the value to check. /// The value to check. /// The error message to display. + /// If the given value is null. [Conditional("DEBUG")] public static void EnsuresNonNull(T value, string message) => Ensures(() => value != null, message); /// - /// Checks if a sequence contains null items as a method post-condition; if any item is null, displays an error - /// message. + /// Checks if a sequence contains null items as a method post-condition; if any item is null, throws an exception that should never be catch. /// /// - /// The check will be performed in "release" mode only to avoid affecting performance. + /// Run in "debug" mode only to not affect performance. /// /// The type of the sequence items. /// The sequence to check. /// The error message to display. + /// If any items the sequence is null. [Conditional("DEBUG")] public static void EnsuresNoNullIn(IEnumerable sequence, string message) => EnsuresAll(sequence, item => item != null, message); /// - /// Checks for a method pre-condition; if the condition is false, displays an error message. + /// Checks for a method pre-condition; if the condition is false, throws an exception that should never be catch. /// - /// - /// The check will be performed in trigger mode only to avoid affecting performance. - /// /// The condition to validate. /// The error message to display. - /// + /// If the specified condition is not satisfied. public static void Requires(Func predicate, string message) => Assert(predicate, message); /// - /// Checks for a method pre-condition against all items in a sequence; if any condition is false, displays an error - /// message. + /// Checks for a method pre-condition against all items in a sequence; if any condition is false, throws an exception that should never be catch. /// /// - /// The check will be performed in "release" mode only to avoid affecting performance. + /// Run in "debug" mode only to not affect performance. /// /// The type of the sequence items. /// The sequence to check. /// The condition to validate. /// The error message to display. + /// If one or more items of the sequence doesn't satisfy the specified condition. [Conditional("DEBUG")] public static void RequiresAll( IEnumerable sequence, - Func predicate, string message) => + Func predicate, string message) => Assert(() => sequence.All(predicate), message); /// - /// Checks for a method pre-condition against all items in a sequence; if all conditions are false, displays an error - /// message. + /// Checks for a method pre-condition against all items in a sequence; if all conditions are false, throws an exception that should never be catch. /// /// - /// The check will be performed in "release" mode only to avoid affecting performance. + /// Run in "debug" mode only to not affect performance. /// /// The type of the sequence items. /// The sequence to check. /// The condition to validate. /// The error message to display. + /// If all items in the sequence doesn't satisfy the specified condition. [Conditional("DEBUG")] public static void RequiresAny( IEnumerable sequence, @@ -138,23 +136,25 @@ public static void RequiresAny( Requires(() => sequence.Any(predicate), message); /// - /// Checks if a value is null as a method post-condition; if the value is null, displays an error message. + /// Checks if a value is null as a method post-condition; if the value is null, throws an exception that should never be catch. /// /// The type of the value to check. /// The value to check. /// The variable name of the value to check. + /// If the given value is null. public static void RequiresNonNull(T value, string variableName) => Requires(() => value != null, $"Receiving null {variableName}."); /// - /// Checks if a sequence contains null items as a method pre-condition; if any item is null, displays an error message. + /// Checks if a sequence contains null items as a method pre-condition; if any item is null, throws an exception that should never be catch. /// /// - /// The check will be performed in "release" mode only to avoid affecting performance. + /// Run in "debug" mode only to not affect performance. /// /// The type of the sequence items. /// The sequence to check. - /// The variable of the sequence to check. + /// The variable name of the sequence to check. + /// If any items the sequence is null. [Conditional("DEBUG")] public static void RequiresNoNullIn(IEnumerable sequence, string variableName) => RequiresAll(sequence, item => item != null, $"Receiving {variableName} containing one or more null items."); diff --git a/src/SolidStack.Core.Guards/Internal/GuardClauseException.cs b/src/SolidStack.Core.Guards/GuardClauseException.cs similarity index 91% rename from src/SolidStack.Core.Guards/Internal/GuardClauseException.cs rename to src/SolidStack.Core.Guards/GuardClauseException.cs index 791a04a..0b001ae 100644 --- a/src/SolidStack.Core.Guards/Internal/GuardClauseException.cs +++ b/src/SolidStack.Core.Guards/GuardClauseException.cs @@ -1,6 +1,6 @@ using System; -namespace SolidStack.Core.Guards.Internal +namespace SolidStack.Core.Guards { /// /// From 196e679fd48083fb6695aa5372cca71ecf41c420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20G=C3=A9linas?= Date: Thu, 24 May 2018 19:13:23 -0400 Subject: [PATCH 3/9] Configure Travis to run all tests projects --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e89574e..f02b8d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,4 @@ language: csharp mono: none dotnet: 2.0.0 script: - - dotnet test ./src/SolidStack.Core.Guards.Tests/SolidStack.Core.Guards.Tests.csproj \ No newline at end of file + - for testsProj in src/**/*Tests.csproj; do dotnet test $testsProj; done \ No newline at end of file From 3e12192ea9f72482082f813aeebfa384f5c38a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20G=C3=A9linas?= Date: Thu, 24 May 2018 19:13:54 -0400 Subject: [PATCH 4/9] Update 'README.md' --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 44d2d03..fc72b54 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The `SolidStack.Core` namespace is the central point of all SolidStack packages, Package | Description ------- | ----------- [SolidStack.Core.Guards][solidstack.core.guards-page] | `SolidStack.Core.Guards` is an extremely simple, unambiguous and lightweight [*guard clause*][guard-clauses-url] library. -SolidStack.Core.Flow (in progress...) | `SolidStack.Core.Flow` focuses on encapsulating the branching logic of your code so you can write a linear and much more readable code flow without having to deal with exceptions, null checks and unnecessary conditions. +[SolidStack.Core.Flow][solidstack.core.flow-page] | `SolidStack.Core.Flow` focuses on encapsulating the branching logic of your code so you can write a linear and much more readable code flow without having to deal with exceptions, null checks and unnecessary conditions. SolidStack.Core.Equality (coming soon...) | `SolidStack.Core.Equality` is primarily useful when you have to tweak the equality of an object to implement the [*Value Object Pattern*][value-object-pattern-url]. All you have to do is use one of the provided abstract classes and the complex equality logic will be done for you. SolidStack.Core.Construction (coming soon...) | `SolidStack.Core.Construction`'s only responsibility is to help you construct objects. You can use the [*Builder Pattern*][builder-pattern-url] provided implementation to build complex objects in a fluent way. @@ -100,6 +100,7 @@ SolidStack is Copyright © 2018 SoftFrame under the [MIT license][license-url]. [nuget-install-url]: http://docs.nuget.org/docs/start-here/installing-nuget [option-pattern-url]: http://www.codinghelmet.com/?path=howto/understanding-the-option-maybe-functional-type [repository-pattern-url]: https://martinfowler.com/eaaCatalog/repository.html -[solidstack.core.guards-page]: https://www.nuget.org/packages/SolidStack.Core +[solidstack.core.guards-page]: https://github.com/softframe/solidstack/wiki/SolidStack.Core.Guards +[solidstack.core.flow-page]: https://github.com/softframe/solidstack/wiki/SolidStack.Core.Flow [unit-of-work-pattern-url]: https://martinfowler.com/eaaCatalog/unitOfWork.html [value-object-pattern-url]: https://martinfowler.com/bliki/ValueObject.html From 2e2439b8ff8c4937f1de8c9f957fa7134c1611d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20G=C3=A9linas?= Date: Thu, 24 May 2018 19:33:09 -0400 Subject: [PATCH 5/9] Add nuspec file --- .../SolidStack.Core.Flow.nuspec | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/SolidStack.Core.Flow/SolidStack.Core.Flow.nuspec diff --git a/src/SolidStack.Core.Flow/SolidStack.Core.Flow.nuspec b/src/SolidStack.Core.Flow/SolidStack.Core.Flow.nuspec new file mode 100644 index 0000000..df714e4 --- /dev/null +++ b/src/SolidStack.Core.Flow/SolidStack.Core.Flow.nuspec @@ -0,0 +1,20 @@ + + + + SolidStack.Core.Flow + $version$ + SolidStack.Core.Guards focuses on encapsulating the branching logic of your code so you can write a linear and much more readable code flow without having to deal with exceptions, null checks and unnecessary conditions. + $authors$ + SolidStack + $projectUrl$ + $licenseUrl$ + $iconUrl$ + $requireLicenseAcceptance$ + + $copyright$ + SolidStack Flow OptionPattern ResultPattern EitherPattern RailwayOrientedProgramming FunctionalProgramming + + + + + \ No newline at end of file From 8ab36c7a336329d1e3fdf620e3f79c4cc2940bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20G=C3=A9linas?= Date: Thu, 24 May 2018 19:33:44 -0400 Subject: [PATCH 6/9] Update 'SolidStack.Core.Guards' nuspec file --- src/SolidStack.Core.Guards/SolidStack.Core.Guards.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SolidStack.Core.Guards/SolidStack.Core.Guards.nuspec b/src/SolidStack.Core.Guards/SolidStack.Core.Guards.nuspec index 25690cf..d7da7c8 100644 --- a/src/SolidStack.Core.Guards/SolidStack.Core.Guards.nuspec +++ b/src/SolidStack.Core.Guards/SolidStack.Core.Guards.nuspec @@ -12,7 +12,7 @@ $requireLicenseAcceptance$ $copyright$ - SolidStack Core Guard Clause + SolidStack GuardClauses Preconditions Postconditions DesignByContract DbC From 23bb63c53a7e6ee256b8685a0e01b5418249feb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20G=C3=A9linas?= Date: Thu, 24 May 2018 20:23:58 -0400 Subject: [PATCH 7/9] Add 'title' variable in nuspec files --- src/SolidStack.Core.Flow/SolidStack.Core.Flow.nuspec | 2 +- src/SolidStack.Core.Guards/SolidStack.Core.Guards.nuspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SolidStack.Core.Flow/SolidStack.Core.Flow.nuspec b/src/SolidStack.Core.Flow/SolidStack.Core.Flow.nuspec index df714e4..dbbd23f 100644 --- a/src/SolidStack.Core.Flow/SolidStack.Core.Flow.nuspec +++ b/src/SolidStack.Core.Flow/SolidStack.Core.Flow.nuspec @@ -5,7 +5,7 @@ $version$ SolidStack.Core.Guards focuses on encapsulating the branching logic of your code so you can write a linear and much more readable code flow without having to deal with exceptions, null checks and unnecessary conditions. $authors$ - SolidStack + $title$ $projectUrl$ $licenseUrl$ $iconUrl$ diff --git a/src/SolidStack.Core.Guards/SolidStack.Core.Guards.nuspec b/src/SolidStack.Core.Guards/SolidStack.Core.Guards.nuspec index d7da7c8..042cb79 100644 --- a/src/SolidStack.Core.Guards/SolidStack.Core.Guards.nuspec +++ b/src/SolidStack.Core.Guards/SolidStack.Core.Guards.nuspec @@ -5,7 +5,7 @@ $version$ SolidStack.Core.Guards is an extremely simple, unambiguous and lightweight guard clause library. $authors$ - SolidStack + $title$ $projectUrl$ $licenseUrl$ $iconUrl$ From 8d4a3f17b0244dac22444125ce13e96e3a3be915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20G=C3=A9linas?= Date: Thu, 24 May 2018 20:48:42 -0400 Subject: [PATCH 8/9] Add folder architecture in VS solution --- .../SolidStack.Core.Flow.csproj | 4 ++++ src/SolidStack.sln | 13 ++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/SolidStack.Core.Flow/SolidStack.Core.Flow.csproj b/src/SolidStack.Core.Flow/SolidStack.Core.Flow.csproj index e75229a..70828b6 100644 --- a/src/SolidStack.Core.Flow/SolidStack.Core.Flow.csproj +++ b/src/SolidStack.Core.Flow/SolidStack.Core.Flow.csproj @@ -4,6 +4,10 @@ netstandard2.0 + + + + diff --git a/src/SolidStack.sln b/src/SolidStack.sln index 7cdc2bf..48f5e42 100644 --- a/src/SolidStack.sln +++ b/src/SolidStack.sln @@ -11,7 +11,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SolidStack.Testing.Xunit", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SolidStack.Core.Flow", "SolidStack.Core.Flow\SolidStack.Core.Flow.csproj", "{193DDAA2-AD28-4892-B6D9-C155EA6BA698}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolidStack.Core.Flow.Tests", "SolidStack.Core.Flow.Tests\SolidStack.Core.Flow.Tests.csproj", "{C0E17594-BC69-4D31-ABD8-E74773D2CAD1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SolidStack.Core.Flow.Tests", "SolidStack.Core.Flow.Tests\SolidStack.Core.Flow.Tests.csproj", "{C0E17594-BC69-4D31-ABD8-E74773D2CAD1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{AD1E680F-CAD7-4F71-A153-010E963A8685}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{50EFF3CA-6249-4DEB-9CAC-D71C1DFFD922}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -43,6 +47,13 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0C7E071B-021D-48F3-9306-A10B38A034D8} = {AD1E680F-CAD7-4F71-A153-010E963A8685} + {3F4308F7-20C6-4E5E-BB94-8681BEEF3E3A} = {AD1E680F-CAD7-4F71-A153-010E963A8685} + {5AE73D94-930C-4AA3-AA1D-704B9B28FB5C} = {50EFF3CA-6249-4DEB-9CAC-D71C1DFFD922} + {193DDAA2-AD28-4892-B6D9-C155EA6BA698} = {AD1E680F-CAD7-4F71-A153-010E963A8685} + {C0E17594-BC69-4D31-ABD8-E74773D2CAD1} = {AD1E680F-CAD7-4F71-A153-010E963A8685} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A31C7CBE-244A-4FA0-9D34-B202D205602E} EndGlobalSection From d4ceb81131611de8d0ce63c09de4bdea89b4aa70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20G=C3=A9linas?= Date: Thu, 24 May 2018 21:06:19 -0400 Subject: [PATCH 9/9] Update 'README.md' --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fc72b54..08f58e0 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Check out our [wiki][docs-url] to explore the documentation provided for the pac # Where can I get it? -First, [install NuGet][nuget-install-url]. Then, install the required NuGet packages avalaible on [nuget.org][nuget-idmobiles-url] using the package manager console: +First, [install NuGet][nuget-install-url]. Then, install the required NuGet packages avalaible on [nuget.org][nuget-softframe-url] using the package manager console: ``` PM> Install-Package @@ -84,18 +84,18 @@ SolidStack is Copyright © 2018 SoftFrame under the [MIT license][license-url]. [builder-pattern-url]: http://www.codinghelmet.com/?path=howto/advances-in-applying-the-builder-design-pattern [build-status-badge]: https://img.shields.io/travis/softframe/solidstack.svg?style=flat-square -[build-url]: https://idmobiles.visualstudio.com/solidstack/_build/index?definitionId=10 -[changelog-url]: https://github.com/idmobiles/solidstack/releases -[develop-branch-url]: https://github.com/idmobiles/solidstack/tree/develop -[docs-url]: https://github.com/idmobiles/solidstack/wiki +[build-url]: https://travis-ci.org/softframe/solidstack +[changelog-url]: https://github.com/softframe/solidstack/releases +[develop-branch-url]: https://github.com/softframe/solidstack/tree/develop +[docs-url]: https://github.com/softframe/solidstack/wiki [ddd-url]: https://en.wikipedia.org/wiki/Domain-driven_design -[github-release-badge]: https://img.shields.io/github/release/idmobiles/solidstack.svg?style=flat-square -[issues-url]: https://github.com/idmobiles/solidstack/issues -[github-release-url]: https://github.com/idmobiles/solidstack/releases +[github-release-badge]: https://img.shields.io/github/release/softframe/solidstack.svg?style=flat-square +[issues-url]: https://github.com/softframe/solidstack/issues +[github-release-url]: https://github.com/softframe/solidstack/releases [guard-clauses-url]: https://medium.com/softframe/what-are-guard-clauses-and-how-to-use-them-350c8f1b6fd2 [license-badge]: https://img.shields.io/badge/license-MIT-orange.svg?style=flat-square -[license-url]: https://github.com/idmobiles/solidstack/blob/master/LICENSE -[new-issue-url]: https://github.com/idmobiles/solidstack/issues/new +[license-url]: https://github.com/softframe/solidstack/blob/master/LICENSE +[new-issue-url]: https://github.com/softframe/solidstack/issues/new [nuget-packages-url]: https://www.nuget.org/profiles/softframe [nuget-install-url]: http://docs.nuget.org/docs/start-here/installing-nuget [option-pattern-url]: http://www.codinghelmet.com/?path=howto/understanding-the-option-maybe-functional-type