diff --git a/OnixLabs.Core.UnitTests/ResultExtensionTests.cs b/OnixLabs.Core.UnitTests/ResultExtensionTests.cs new file mode 100644 index 0000000..236d2de --- /dev/null +++ b/OnixLabs.Core.UnitTests/ResultExtensionTests.cs @@ -0,0 +1,180 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Xunit; + +namespace OnixLabs.Core.UnitTests; + +public sealed class ResultExtensionTests +{ + [Fact(DisplayName = "Result.GetValueOrNone should return the Optional value when the result is Success and the Optional value is not None")] + public void ResultGetValueOrNoneShouldReturnOptionalValueWhenResultIsSuccessAndOptionalValueIsNotNone() + { + // Given + Optional expectedNumber = 123; + Optional expectedText = "abc"; + Result> numberResult = expectedNumber; + Result> textResult = expectedText; + + // When + Optional actualNumber = numberResult.GetValueOrNone(); + Optional actualText = textResult.GetValueOrNone(); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetValueOrNone should return None value when the result is Success and the Optional value is None")] + public void ResultGetValueOrNoneShouldReturnNoneWhenResultIsSuccessAndOptionalValueIsNotNone() + { + // Given + Optional expectedNumber = Optional.None; + Optional expectedText = Optional.None; + Result> numberResult = expectedNumber; + Result> textResult = expectedText; + + // When + Optional actualNumber = numberResult.GetValueOrNone(); + Optional actualText = textResult.GetValueOrNone(); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetValueOrNone should return None value when the result is Failure")] + public void ResultGetValueOrNoneShouldReturnNoneWhenResultIsFailure() + { + // Given + Optional expectedNumber = Optional.None; + Optional expectedText = Optional.None; + Result> numberResult = Result>.Failure(new Exception("Result has failed.")); + Result> textResult = Result>.Failure(new Exception("Result has failed.")); + + // When + Optional actualNumber = numberResult.GetValueOrNone(); + Optional actualText = textResult.GetValueOrNone(); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetOptionalValueOrThrow should return the Optional value when the Result is Success and the Optional value is not None")] + public void ResultGetOptionalValueOrThrowShouldReturnOptionalValueWhenResultIsSuccessAndOptionalValueIsNotNone() + { + // Given + const int expectedNumber = 123; + const string expectedText = "abc"; + Result> numberResult = Optional.Some(expectedNumber); + Result> textResult = Optional.Some(expectedText); + + // When + int actualNumber = numberResult.GetOptionalValueOrThrow(); + string actualText = textResult.GetOptionalValueOrThrow(); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetOptionalValueOrThrow should throw InvalidOperationException when the Result is Success and the Optional value is None")] + public void ResultGetOptionalValueOrThrowShouldThrowInvalidOperationExceptionWhenResultIsSuccessAndOptionalValueIsNone() + { + // Given + Result> numberResult = Optional.None; + Result> textResult = Optional.None; + + // When + Exception numberException = Assert.Throws(() => numberResult.GetOptionalValueOrThrow()); + Exception textException = Assert.Throws(() => textResult.GetOptionalValueOrThrow()); + + // Then + Assert.Equal("Optional value of type System.Int32 is not present.", numberException.Message); + Assert.Equal("Optional value of type System.String is not present.", textException.Message); + } + + [Fact(DisplayName = "Result.GetOptionalValueOrThrow should throw InvalidOperationException when the Result is Failure")] + public void ResultGetOptionalValueOrThrowShouldThrowInvalidOperationExceptionWhenResultIsFailure() + { + // Given + Result> numberResult = Result>.Failure(new Exception("Result has failed.")); + Result> textResult = Result>.Failure(new Exception("Result has failed.")); + + // When + Exception numberException = Assert.Throws(() => numberResult.GetOptionalValueOrThrow()); + Exception textException = Assert.Throws(() => textResult.GetOptionalValueOrThrow()); + + // Then + Assert.Equal("Result has failed.", numberException.Message); + Assert.Equal("Result has failed.", textException.Message); + } + + + [Fact(DisplayName = "Result.GetOptionalValueOrDefault should return the Optional value when the Result is Success and the Optional value is not None")] + public void ResultGetOptionalValueOrDefaultShouldReturnOptionalValueWhenResultIsSuccessAndOptionalValueIsNotNone() + { + // Given + const int expectedNumber = 123; + const string expectedText = "abc"; + Result> numberResult = Optional.Some(expectedNumber); + Result> textResult = Optional.Some(expectedText); + + // When + int actualNumber = numberResult.GetOptionalValueOrDefault(456); + string actualText = textResult.GetOptionalValueOrDefault("xyz"); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetOptionalValueOrDefault should throw return the default value when the Result is Success and the Optional value is None")] + public void ResultGetOptionalValueOrThrowShouldReturnDefaultValueWhenResultIsSuccessAndOptionalValueIsNone() + { + // Given + const int expectedNumber = 456; + const string expectedText = "xyz"; + Result> numberResult = Optional.None; + Result> textResult = Optional.None; + + // When + int actualNumber = numberResult.GetOptionalValueOrDefault(expectedNumber); + string actualText = textResult.GetOptionalValueOrDefault(expectedText); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } + + [Fact(DisplayName = "Result.GetOptionalValueOrDefault should return default value when the result is Failure")] + public void ResultGetOptionalValueOrDefaultShouldReturnDefaultValueWhenResultIsFailure() + { + // Given + const int expectedNumber = 456; + const string expectedText = "xyz"; + Result> numberResult = Result>.Failure(new Exception("Result has failed.")); + Result> textResult = Result>.Failure(new Exception("Result has failed.")); + + // When + int actualNumber = numberResult.GetOptionalValueOrDefault(expectedNumber); + string actualText = textResult.GetOptionalValueOrDefault(expectedText); + + // Then + Assert.Equal(expectedNumber, actualNumber); + Assert.Equal(expectedText, actualText); + } +} diff --git a/OnixLabs.Core/Extensions.Result.cs b/OnixLabs.Core/Extensions.Result.cs new file mode 100644 index 0000000..be92f8e --- /dev/null +++ b/OnixLabs.Core/Extensions.Result.cs @@ -0,0 +1,64 @@ +// Copyright 2020 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.ComponentModel; + +namespace OnixLabs.Core; + +/// +/// Provides extension methods for instances. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public static class ResultExtensions +{ + /// + /// Gets the value of the current instance, + /// or if the result is in a state. + /// + /// The instance from which to obtain the value. + /// The underlying type of the value. + /// + /// Return the value of the current instance, + /// or if the result is in a state. + /// + public static Optional GetValueOrNone(this Result> result) where T : notnull => + result is Success> { Value.HasValue: true } ? result.GetValueOrThrow() : Optional.None; + + /// + /// Gets the underlying value from the current of , + /// or throws an exception if the result is in a state, or the is . + /// + /// The instance from which to obtain the value. + /// The underlying type of the value. + /// + /// Returns the underlying value from the current of , + /// or throws an exception if the result is in a state, or the is . + /// + public static T GetOptionalValueOrThrow(this Result> result) where T : notnull => + result.GetValueOrThrow().GetValueOrThrow(); + + /// + /// Gets the underlying value from the current of , + /// or returns the default value if the result is in a state, or the is . + /// + /// The instance from which to obtain the value. + /// The default value to return if the result is in a state, or the is . + /// The underlying type of the value. + /// + /// Returns the underlying value from the current of , + /// or returns the default value if the result is in a state, or the is . + /// + public static T GetOptionalValueOrDefault(this Result> result, T defaultValue) where T : notnull => + result is Success> { Value.HasValue: true } ? result.GetOptionalValueOrThrow() : defaultValue; +}