diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 32f8ed7..6e47d26 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -9,6 +9,9 @@ on: branches: - '**' +env: + USE_FULL_NUMERIC_PROVIDER: "true" + jobs: build: diff --git a/OnixLabs.Core.UnitTests.Data/Record.cs b/OnixLabs.Core.UnitTests.Data/Record.cs index ff6927a..c2fb53d 100644 --- a/OnixLabs.Core.UnitTests.Data/Record.cs +++ b/OnixLabs.Core.UnitTests.Data/Record.cs @@ -14,4 +14,4 @@ namespace OnixLabs.Core.UnitTests.Data; -public sealed record Record(string Text, int Number, T Value); +public sealed record Record(string Text, int Number, T Value, IEnumerable? Values = null); diff --git a/OnixLabs.Core.UnitTests/ArrayExtensionTests.cs b/OnixLabs.Core.UnitTests/ArrayExtensionTests.cs index e53e0df..ee7853d 100644 --- a/OnixLabs.Core.UnitTests/ArrayExtensionTests.cs +++ b/OnixLabs.Core.UnitTests/ArrayExtensionTests.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Text; using Xunit; namespace OnixLabs.Core.UnitTests; @@ -62,4 +63,18 @@ public void ConcatenateWithShouldProduceExpectedResult() // Then Assert.Equal(expected, actual); } + + [Fact(DisplayName = "Array.ToString should produce the expected result")] + public void ToStringShouldProduceExpectedResult() + { + // Given + const string expected = "ABCxyz123"; + byte[] bytes = expected.ToByteArray(); + + // When + string actual = bytes.ToString(Encoding.UTF8); + + // Then + Assert.Equal(expected, actual); + } } diff --git a/OnixLabs.Core.UnitTests/Linq/IEnumerableExtensionTests.cs b/OnixLabs.Core.UnitTests/Linq/IEnumerableExtensionTests.cs index d5cf15a..5035b48 100644 --- a/OnixLabs.Core.UnitTests/Linq/IEnumerableExtensionTests.cs +++ b/OnixLabs.Core.UnitTests/Linq/IEnumerableExtensionTests.cs @@ -13,8 +13,10 @@ // limitations under the License. using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Numerics; using OnixLabs.Core.Linq; using OnixLabs.Core.UnitTests.Data; @@ -25,6 +27,19 @@ namespace OnixLabs.Core.UnitTests.Linq; // ReSharper disable InconsistentNaming public sealed class IEnumerableExtensionTests { + [Fact(DisplayName = "IEnumerable.AllEqualBy should return true when the enumerable is empty")] + public void AllEqualByShouldProduceExpectedResultTrueWhenEnumerableIsEmpty() + { + // Given + IEnumerable> elements = []; + + // When + bool result = elements.AllEqualBy(element => element.Text); + + // Then + Assert.True(result); + } + [Fact(DisplayName = "IEnumerable.AllEqualBy should return true when all items are equal by the same property")] public void AllEqualByShouldProduceExpectedResultTrue() { @@ -89,6 +104,20 @@ public void AnyEqualByShouldProduceExpectedResultFalse() Assert.False(result); } + [Fact(DisplayName = "IEnumerable.Count should return the count of all elements (non-generic)")] + public void CountShouldReturnCountOfAllElementsNonGeneric() + { + // Given + const int expected = 3; + IEnumerable elements = new[] { 1, 2, 3 }; + + // When + int actual = elements.Count(); + + // Then + Assert.Equal(expected, actual); + } + [Fact(DisplayName = "IEnumerable.CountNot should produce the expected result.")] public void CountNotShouldProduceExpectedResult() { @@ -162,6 +191,19 @@ public void FirstOrNoneShouldReturnNoneWhenNoElementMatchesPredicateAndCollectio Assert.Equal(expected, actual); } + [Fact(DisplayName = "IEnumerable.ForEach should iterate over every element in the enumerable (non-generic)")] + public void ForEachShouldProduceExpectedResultNonGeneric() + { + // Given + IEnumerable enumerable = new[] { new Element(), new Element(), new Element() }; + + // When + enumerable.ForEach(element => (element as Element)!.Called = true); + + // Then + Assert.All(enumerable.Cast(), element => Assert.True(element!.Called)); + } + [Fact(DisplayName = "IEnumerable.ForEach should iterate over every element in the enumerable")] public void ForEachShouldProduceExpectedResult() { @@ -175,6 +217,21 @@ public void ForEachShouldProduceExpectedResult() Assert.All(enumerable, element => Assert.True(element.Called)); } + [Fact(DisplayName = "IEnumerable.GetContentHashCode should produce equal hash codes (non-generic)")] + public void GetContentHashCodeShouldProduceExpectedResultEqualNonGeneric() + { + // Given + IEnumerable enumerable1 = new[] { new Element(1), new Element(2), new Element(3) }; + IEnumerable enumerable2 = new[] { new Element(1), new Element(2), new Element(3) }; + + // When + int hashCode1 = enumerable1.GetContentHashCode(); + int hashCode2 = enumerable2.GetContentHashCode(); + + // Then + Assert.Equal(hashCode1, hashCode2); + } + [Fact(DisplayName = "IEnumerable.GetContentHashCode should produce equal hash codes")] public void GetContentHashCodeShouldProduceExpectedResultEqual() { @@ -205,6 +262,32 @@ public void GetContentHashCodeShouldProduceExpectedResultDifferent() Assert.NotEqual(hashCode1, hashCode2); } + [Fact(DisplayName = "IEnumerable.IsEmpty should return true when the enumerable is empty (non-generic)")] + public void IsEmptyShouldProduceExpectedResultTrueNonGeneric() + { + // Given + IEnumerable enumerable = Array.Empty(); + + // When + bool result = enumerable.IsEmpty(); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IEnumerable.IsEmpty should return false when the enumerable is not empty (non-generic)")] + public void IsEmptyShouldProduceExpectedResultFalseNonGeneric() + { + // Given + IEnumerable enumerable = new[] { new Element() }; + + // When + bool result = enumerable.IsEmpty(); + + // Then + Assert.False(result); + } + [Fact(DisplayName = "IEnumerable.IsEmpty should return true when the enumerable is empty")] public void IsEmptyShouldProduceExpectedResultTrue() { @@ -231,6 +314,32 @@ public void IsEmptyShouldProduceExpectedResultFalse() Assert.False(result); } + [Fact(DisplayName = "IEnumerable.IsNotEmpty should return true when the enumerable is not empty (non-generic)")] + public void IsNotEmptyShouldProduceExpectedResultTrueNonGeneric() + { + // Given + IEnumerable enumerable = new[] { new Element() }; + + // When + bool result = enumerable.IsNotEmpty(); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IEnumerable.IsNotEmpty should return false when the enumerable is empty (non-generic)")] + public void IsNotEmptyShouldProduceExpectedResultFalseNonGeneric() + { + // Given + IEnumerable enumerable = Array.Empty(); + + // When + bool result = enumerable.IsNotEmpty(); + + // Then + Assert.False(result); + } + [Fact(DisplayName = "IEnumerable.IsNotEmpty should return true when the enumerable is not empty")] public void IsNotEmptyShouldProduceExpectedResultTrue() { @@ -257,6 +366,45 @@ public void IsNotEmptyShouldProduceExpectedResultFalse() Assert.False(result); } + [Fact(DisplayName = "IEnumerable.IsSingle should return true when the enumerable contains a single element (non-generic)")] + public void IsSingleShouldProduceExpectedResultTrueNonGeneric() + { + // Given + IEnumerable enumerable = new[] { new Element() }; + + // When + bool result = enumerable.IsSingle(); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IEnumerable.IsSingle should return false when the enumerable is empty (non-generic)")] + public void IsSingleShouldProduceExpectedResultFalseWhenEmptyNonGeneric() + { + // Given + IEnumerable enumerable = Array.Empty(); + + // When + bool result = enumerable.IsSingle(); + + // Then + Assert.False(result); + } + + [Fact(DisplayName = "IEnumerable.IsSingle should return false when the enumerable contains more than one element (non-generic)")] + public void IsSingleShouldProduceExpectedResultFalseWhenMoreThanOneElementNonGeneric() + { + // Given + IEnumerable enumerable = new[] { new Element(), new Element() }; + + // When + bool result = enumerable.IsSingle(); + + // Then + Assert.False(result); + } + [Fact(DisplayName = "IEnumerable.IsSingle should return true when the enumerable contains a single element")] public void IsSingleShouldProduceExpectedResultTrue() { @@ -296,6 +444,32 @@ public void IsSingleShouldProduceExpectedResultFalseWhenMoreThanOneElement() Assert.False(result); } + [Fact(DisplayName = "IEnumerable.IsCountEven should return true when the enumerable contains an even number of elements (non-generic)")] + public void IsCountEvenShouldProduceExpectedResultTrueNonGeneric() + { + // Given + IEnumerable enumerable = new[] { new Element(), new Element() }; + + // When + bool result = enumerable.IsCountEven(); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IEnumerable.IsCountEven should return false when the enumerable contains an odd number of elements (non-generic)")] + public void IsCountEvenShouldProduceExpectedResultFalseNonGeneric() + { + // Given + IEnumerable enumerable = new[] { new Element() }; + + // When + bool result = enumerable.IsCountEven(); + + // Then + Assert.False(result); + } + [Fact(DisplayName = "IEnumerable.IsCountEven should return true when the enumerable contains an even number of elements")] public void IsCountEvenShouldProduceExpectedResultTrue() { @@ -322,6 +496,32 @@ public void IsCountEvenShouldProduceExpectedResultFalse() Assert.False(result); } + [Fact(DisplayName = "IEnumerable.IsCountOdd should return true when the enumerable contains an odd number of elements (non-generic)")] + public void IsCountOddShouldProduceExpectedResultTrueNonGeneric() + { + // Given + IEnumerable enumerable = new[] { new Element() }; + + // When + bool result = enumerable.IsCountOdd(); + + // Then + Assert.True(result); + } + + [Fact(DisplayName = "IEnumerable.IsCountOdd should return false when the enumerable contains an even number of elements (non-generic)")] + public void IsCountOddShouldProduceExpectedResultFalseNonGeneric() + { + // Given + IEnumerable enumerable = new[] { new Element(), new Element() }; + + // When + bool result = enumerable.IsCountOdd(); + + // Then + Assert.False(result); + } + [Fact(DisplayName = "IEnumerable.IsCountOdd should return true when the enumerable contains an odd number of elements")] public void IsCountOddShouldProduceExpectedResultTrue() { @@ -348,6 +548,34 @@ public void IsCountOddShouldProduceExpectedResultFalse() Assert.False(result); } + [Fact(DisplayName = "IEnumerable.JoinToString should produce the expected result with the default separator (non-generic)")] + public void JoinToStringShouldProduceExpectedResultWithDefaultSeparatorNonGeneric() + { + // Given + IEnumerable enumerable = new object[] { 1, 2, 3, 4.5, true, false }; + const string expected = "1, 2, 3, 4.5, True, False"; + + // When + string actual = enumerable.JoinToString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "IEnumerable.JoinToString should produce the expected result with a custom separator (non-generic)")] + public void JoinToStringShouldProduceExpectedResultWithCustomSeparatorNonGeneric() + { + // Given + IEnumerable enumerable = new object[] { 1, 2, 3, 4.5, true, false }; + const string expected = "1 *$ 2 *$ 3 *$ 4.5 *$ True *$ False"; + + // When + string actual = enumerable.JoinToString(" *$ "); + + // Then + Assert.Equal(expected, actual); + } + [Fact(DisplayName = "IEnumerable.JoinToString should produce the expected result with the default separator")] public void JoinToStringShouldProduceExpectedResultWithDefaultSeparator() { @@ -691,8 +919,8 @@ public void WhereNotShouldProduceExpectedResult() Assert.Equal(expected, actual); } - [Fact(DisplayName = "IEnumerable.WhereNotNull should produce the expected result")] - public void WhereNotNullShouldProduceExpectedResult() + [Fact(DisplayName = "IEnumerable.WhereNotNull should produce the expected result (class)")] + public void WhereNotNullShouldProduceExpectedResultClass() { // Given Record element1 = new("abc", 123, Guid.NewGuid()); @@ -707,6 +935,62 @@ public void WhereNotNullShouldProduceExpectedResult() Assert.Equal(expected, actual); } + [Fact(DisplayName = "IEnumerable.WhereNotNull should produce the expected result (struct)")] + public void WhereNotNullShouldProduceExpectedResultStruct() + { + // Given + IEnumerable elements = [1, 2, null, 3, null, 4, 5]; + IEnumerable expected = [1, 2, 3, 4, 5]; + + // When + IEnumerable actual = elements.WhereNotNull(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "IEnumerable.ToCollectionString should produce expected result (Object, non-generic)")] + public void ToCollectionStringShouldProduceExpectedResultObjectNonGeneric() + { + // Given + IEnumerable values = new object[] { 123, "abc", true, 123.456 }; + const string expected = "[123, abc, True, 123.456]"; + + // When + string actual = values.ToCollectionString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "IEnumerable.ToCollectionString should produce expected result (String, non-generic)")] + public void ToCollectionStringShouldProduceExpectedResultStringNonGeneric() + { + // Given + IEnumerable values = new[] { "abc", "xyz", "123" }; + const string expected = "[abc, xyz, 123]"; + + // When + string actual = values.ToCollectionString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "IEnumerable.ToCollectionString should produce expected result (Int32, non-generic)")] + public void ToCollectionStringShouldProduceExpectedResultInt32NonGeneric() + { + // Given + IEnumerable values = new[] { 0, 1, 12, 123, 1234, -1, -12, -123, -1234 }; + const string expected = "[0, 1, 12, 123, 1234, -1, -12, -123, -1234]"; + + // When + string actual = values.ToCollectionString(); + + // Then + Assert.Equal(expected, actual); + } + [Fact(DisplayName = "IEnumerable.ToCollectionString should produce expected result (Object)")] public void ToCollectionStringShouldProduceExpectedResultObject() { diff --git a/OnixLabs.Core.UnitTests/ObjectExtensionTests.cs b/OnixLabs.Core.UnitTests/ObjectExtensionTests.cs index aeb1a46..1c82dc2 100644 --- a/OnixLabs.Core.UnitTests/ObjectExtensionTests.cs +++ b/OnixLabs.Core.UnitTests/ObjectExtensionTests.cs @@ -70,7 +70,7 @@ public void CompareToObjectShouldProducePositiveOneIfTheSpecifiedObjectIsNull() const int expected = 1; // When - int actual = 124.CompareToObject(null); + int actual = 123.CompareToObject(null); // Then Assert.Equal(expected, actual); @@ -109,15 +109,71 @@ public void CompareToObjectShouldThrowArgumentExceptionIfSpecifiedObjectIsOfInco Exception exception = Assert.Throws(() => 122.CompareToObject(123.456)); // Then - Assert.Equal("Object must be of type System.Int32 (Parameter 'obj')", exception.Message); + Assert.Equal("Object must be of type System.Int32 (Parameter 'right')", exception.Message); + } + + [Fact(DisplayName = "ToRecordString should produce null when the object is null")] + public void ToRecordStringShouldProduceNullWhenObjectIsNull() + { + // Given + const string expected = "null"; + Record? record = null; + + // When + string actual = record.ToRecordString(); + + // Then + Assert.Equal(expected, actual); } [Fact(DisplayName = "ToRecordString should produce a record formatted string")] public void ToRecordStringShouldProduceExpectedResult() { // Given - Record record = new("abc", 123, Guid.NewGuid()); - string expected = record.ToString(); + const string expected = "Record { Text = abc, Number = 123, Value = 123.456, Values = null }"; + Record record = new("abc", 123, 123.456m); + + // When + string actual = record.ToRecordString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "ToRecordString should produce a record formatted string with nullable property")] + public void ToRecordStringShouldProduceExpectedResultWithNullableProperty() + { + // Given + const string expected = "Record { Text = abc, Number = 123, Value = null, Values = null }"; + Record record = new("abc", 123, null); + + // When + string actual = record.ToRecordString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "ToRecordString should produce a record formatted string with collection")] + public void ToRecordStringShouldProduceExpectedResultWithCollection() + { + // Given + const string expected = "Record { Text = abc, Number = 123, Value = null, Values = [1, 2, 1.23, null] }"; + Record record = new("abc", 123, null, [1, 2, 1.23m, null]); + + // When + string actual = record.ToRecordString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "ToRecordString should produce an empty record")] + public void ToRecordStringShouldProduceEmptyRecord() + { + // Given + const string expected = "Int32 { }"; + const int record = 123; // When string actual = record.ToRecordString(); @@ -125,4 +181,46 @@ public void ToRecordStringShouldProduceExpectedResult() // Then Assert.Equal(expected, actual); } + + [Fact(DisplayName = "ToRecordString catch should produce an empty record")] + public void ToRecordStringCatchShouldProduceEmptyRecord() + { + // Given + const string expected = "String { }"; + const string record = ""; + + // When + string actual = record.ToRecordString(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "ToStringOrNull should produce null when the object is null")] + public void ToStringOrNullShouldProduceNullWhenObjectIsNull() + { + // Given + const string expected = "null"; + object? value = null; + + // When + string actual = value.ToStringOrNull(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "ToStringOrNull should produce expected result when the object is not null")] + public void ToStringOrNullShouldProduceExpectedResultWhenObjectIsNotNull() + { + // Given + const string expected = "abc"; + const string value = "abc"; + + // When + string actual = value.ToStringOrNull(); + + // Then + Assert.Equal(expected, actual); + } } diff --git a/OnixLabs.Core.UnitTests/OptionalTests.cs b/OnixLabs.Core.UnitTests/OptionalTests.cs index eed14d1..18b2507 100644 --- a/OnixLabs.Core.UnitTests/OptionalTests.cs +++ b/OnixLabs.Core.UnitTests/OptionalTests.cs @@ -27,10 +27,12 @@ public void OptionalNoneShouldProduceExpectedResult() Optional text = Optional.None; // Then + Assert.True(Optional.IsNone(number)); Assert.False(number.HasValue); Assert.IsType>(number); Assert.Equal(Optional.None, number); + Assert.True(Optional.IsNone(text)); Assert.False(text.HasValue); Assert.IsType>(text); Assert.Equal(Optional.None, text); @@ -44,10 +46,12 @@ public void OptionalOfShouldProduceExpectedResultForImplicitDefaultValues() Optional text = Optional.Of(default); // Then + Assert.True(Optional.IsNone(number)); Assert.False(number.HasValue); Assert.IsType>(number); Assert.Equal(Optional.None, number); + Assert.True(Optional.IsNone(text)); Assert.False(text.HasValue); Assert.IsType>(text); Assert.Equal(Optional.None, text); @@ -61,10 +65,12 @@ public void OptionalOfShouldProduceExpectedResultForExplicitDefaultValues() Optional text = Optional.Of(null); // Then + Assert.True(Optional.IsNone(number)); Assert.False(number.HasValue); Assert.IsType>(number); Assert.Equal(Optional.None, number); + Assert.True(Optional.IsNone(text)); Assert.False(text.HasValue); Assert.IsType>(text); Assert.Equal(Optional.None, text); @@ -78,10 +84,12 @@ public void OptionalOfShouldProduceExpectedResultForExplicitNonDefaultValues() Optional text = Optional.Of("abc"); // Then + Assert.True(Optional.IsSome(number)); Assert.True(number.HasValue); Assert.IsType>(number); Assert.Equal(123, number); + Assert.True(Optional.IsSome(text)); Assert.True(text.HasValue); Assert.IsType>(text); Assert.Equal("abc", text); @@ -110,10 +118,12 @@ public void OptionalOfShouldProduceExpectedResultForNonNullNullableStructValues( Optional identifier = Optional.Of((Guid?)Guid.Empty); // Then + Assert.True(Optional.IsSome(number)); Assert.True(number.HasValue); Assert.IsType>(number); Assert.Equal(123, number); + Assert.True(Optional.IsSome(identifier)); Assert.True(identifier.HasValue); Assert.IsType>(identifier); Assert.Equal(Guid.Empty, identifier); @@ -127,10 +137,12 @@ public void OptionalSomeShouldProduceExpectedResult() Optional text = Optional.Some("abc"); // Then + Assert.True(Optional.IsSome(number)); Assert.True(number.HasValue); Assert.IsType>(number); Assert.Equal(123, number); + Assert.True(Optional.IsSome(text)); Assert.True(text.HasValue); Assert.IsType>(text); Assert.Equal("abc", text); @@ -144,10 +156,12 @@ public void OptionalImplicitOperatorShouldProduceExpectedSomeResult() Optional text = "abc"; // Then + Assert.True(Optional.IsSome(number)); Assert.True(number.HasValue); Assert.IsType>(number); Assert.Equal(123, number); + Assert.True(Optional.IsSome(text)); Assert.True(text.HasValue); Assert.IsType>(text); Assert.Equal("abc", text); diff --git a/OnixLabs.Core.UnitTests/PreconditionTests.cs b/OnixLabs.Core.UnitTests/PreconditionTests.cs index 42edf9b..22abb3d 100644 --- a/OnixLabs.Core.UnitTests/PreconditionTests.cs +++ b/OnixLabs.Core.UnitTests/PreconditionTests.cs @@ -165,24 +165,6 @@ public void RequireShouldNotThrowInvalidOperationExceptionWhenConditionIsTrue() Require(true); } - [Fact(DisplayName = "RequireWithinRange should throw an ArgumentOutOfRangeException when the condition is false")] - public void RequireWithinRangeShouldThrowArgumentOutOfRangeExceptionWhenConditionIsFalse() - { - // When - Exception exception = Assert.Throws(() => RequireWithinRange(false)); - - // Then - Assert.Equal("Argument must be within range.", exception.Message); - } - - [Theory(DisplayName = "RequireWithinRange should not throw an ArgumentOutOfRangeException when the condition is true")] - [InlineData(100, 0, 123)] - public void RequireWithinRangeShouldNotThrowArgumentOutOfRangeExceptionWhenConditionIsTrue(int value, int min, int max) - { - // Given / When / Then - RequireWithinRange(value >= min && value <= max); - } - [Fact(DisplayName = "RequireWithinRangeInclusive should throw an ArgumentOutOfRangeException when the value falls below the specified range")] public void RequireWithinRangeInclusiveShouldThrowArgumentOutOfRangeExceptionWhenValueFallsBelowSpecifiedRange() { diff --git a/OnixLabs.Core.UnitTests/Reflection/TypeExtensionTests.cs b/OnixLabs.Core.UnitTests/Reflection/TypeExtensionTests.cs index 063e94d..c7d5645 100644 --- a/OnixLabs.Core.UnitTests/Reflection/TypeExtensionTests.cs +++ b/OnixLabs.Core.UnitTests/Reflection/TypeExtensionTests.cs @@ -22,13 +22,23 @@ namespace OnixLabs.Core.UnitTests.Reflection; public sealed class TypeExtensionTests { [Theory(DisplayName = "Type.GetName should produce the expected result")] - [InlineData(typeof(object), "Object")] - [InlineData(typeof(List<>), "List")] - [InlineData(typeof(Dictionary<,>), "Dictionary")] - public void TypeGetNameShouldProduceExpectedResult(Type type, string expected) + [InlineData(typeof(object), TypeNameFlags.None, "Object")] + [InlineData(typeof(List<>), TypeNameFlags.None, "List")] + [InlineData(typeof(Dictionary<,>), TypeNameFlags.None, "Dictionary")] + [InlineData(typeof(object), TypeNameFlags.UseFullNames, "System.Object")] + [InlineData(typeof(List<>), TypeNameFlags.UseFullNames, "System.Collections.Generic.List")] + [InlineData(typeof(Dictionary<,>), TypeNameFlags.UseFullNames, "System.Collections.Generic.Dictionary")] + [InlineData(typeof(object), TypeNameFlags.UseGenericTypeArguments, "Object")] + [InlineData(typeof(List<>), TypeNameFlags.UseGenericTypeArguments, "List<>")] + [InlineData(typeof(Dictionary<,>), TypeNameFlags.UseGenericTypeArguments, "Dictionary<>")] + [InlineData(typeof(List>), TypeNameFlags.UseGenericTypeArguments, "List>")] + [InlineData(typeof(Dictionary>), TypeNameFlags.UseGenericTypeArguments, "Dictionary>")] + [InlineData(typeof(List>), TypeNameFlags.All, "System.Collections.Generic.List>")] + [InlineData(typeof(Dictionary>), TypeNameFlags.All, "System.Collections.Generic.Dictionary>")] + public void TypeGetNameShouldProduceExpectedResult(Type type, TypeNameFlags flags, string expected) { // When - string actual = type.GetName(); + string actual = type.GetName(flags); // Then Assert.Equal(expected, actual); diff --git a/OnixLabs.Core.UnitTests/ResultExtensionTests.cs b/OnixLabs.Core.UnitTests/ResultExtensionTests.cs index 477e3d0..7ec61a6 100644 --- a/OnixLabs.Core.UnitTests/ResultExtensionTests.cs +++ b/OnixLabs.Core.UnitTests/ResultExtensionTests.cs @@ -57,7 +57,7 @@ public async Task ResultSuccessGetExceptionOrThrowAsyncShouldProduceExpectedResu Exception exception = await Assert.ThrowsAsync(async () => await Task.FromResult(result).GetExceptionOrThrowAsync()); // Then - Assert.Equal("The current result is not in a Failure state.", exception.Message); + Assert.Equal("The current result is not in a failure state.", exception.Message); } [Fact(DisplayName = "Result Failure.GetExceptionOrDefaultAsync should produce the expected result.")] @@ -139,7 +139,7 @@ public async Task ResultOfTSuccessGetExceptionOrThrowAsyncShouldProduceExpectedR Exception exception = await Assert.ThrowsAsync(async () => await Task.FromResult(result).GetExceptionOrThrowAsync()); // Then - Assert.Equal("The current result is not in a Failure state.", exception.Message); + Assert.Equal("The current result is not in a failure state.", exception.Message); } [Fact(DisplayName = "Result Failure.GetExceptionOrDefaultAsync should produce the expected result.")] diff --git a/OnixLabs.Core.UnitTests/ResultGenericTests.cs b/OnixLabs.Core.UnitTests/ResultGenericTests.cs index 296f75b..4dc05e6 100644 --- a/OnixLabs.Core.UnitTests/ResultGenericTests.cs +++ b/OnixLabs.Core.UnitTests/ResultGenericTests.cs @@ -22,834 +22,667 @@ namespace OnixLabs.Core.UnitTests; public sealed class ResultGenericTests { - [Fact(DisplayName = "Result.Of should produce expected success result")] + private static readonly Exception FailureException = new("Failure"); + + [Fact(DisplayName = "Result.IsSuccess should produce the expected result")] + public void ResultIsSuccessShouldProduceExpectedResult() + { + // Given + Result result = Result.Success(1); + + // When / Then + Assert.True(result.IsSuccess); + Assert.False(result.IsFailure); + } + + [Fact(DisplayName = "Result.IsFailure should produce the expected result")] + public void ResultIsFailureShouldProduceExpectedResult() + { + // Given + Result result = Result.Failure(FailureException); + + // When / Then + Assert.True(result.IsFailure); + Assert.False(result.IsSuccess); + } + + [Fact(DisplayName = "Result.Of should produce the expected success result.")] public void ResultOfShouldProduceExpectedSuccessResult() { // Given / When - Result number = Result.Of(() => 123); - Result text = Result.Of(() => "abc"); + Result result = Result.Of(() => 1); // Then - Assert.True(number.IsSuccess); - Assert.False(number.IsFailure); - Assert.IsType>(number); - Assert.Equal(123, number); - - Assert.True(text.IsSuccess); - Assert.False(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("abc", text); + Assert.IsType>(result); } - [Fact(DisplayName = "Result.Of should produce expected failure result")] + [Fact(DisplayName = "Result.Of should produce the expected failure result.")] public void ResultOfShouldProduceExpectedFailureResult() { // Given / When - Exception exception = new("failure"); - Result number = Result.Of(() => throw exception); - Result text = Result.Of(() => throw exception); + Result result = Result.Of(() => throw FailureException); // Then - Assert.False(number.IsSuccess); - Assert.True(number.IsFailure); - Assert.IsType>(number); - Assert.Equal("failure", (number as Failure)!.Exception.Message); - - Assert.False(text.IsSuccess); - Assert.True(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("failure", (text as Failure)!.Exception.Message); + Assert.IsType>(result); + Assert.Equal(FailureException, result.GetExceptionOrThrow()); } - [Fact(DisplayName = "Result.OfAsync should produce expected success result")] + [Fact(DisplayName = "Result.OfAsync should produce the expected success result.")] public async Task ResultOfAsyncShouldProduceExpectedSuccessResult() { // Given / When - Result number = await Result.OfAsync(async () => await Task.FromResult(123)); - Result text = await Result.OfAsync(async () => await Task.FromResult("abc")); + Result result = await Result.OfAsync(async () => await Task.FromResult(1)); // Then - Assert.True(number.IsSuccess); - Assert.False(number.IsFailure); - Assert.IsType>(number); - Assert.Equal(123, number); - - Assert.True(text.IsSuccess); - Assert.False(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("abc", text); + Assert.IsType>(result); } - [Fact(DisplayName = "Result.OfAsync should produce expected failure result")] + [Fact(DisplayName = "Result.OfAsync should produce the expected failure result.")] public async Task ResultOfAsyncShouldProduceExpectedFailureResult() { // Given / When - Exception exception = new("failure"); - Result number = await Result.OfAsync(() => throw exception); - Result text = await Result.OfAsync(() => throw exception); + Result result = await Result.OfAsync(async () => await Task.FromException(FailureException)); // Then - Assert.False(number.IsSuccess); - Assert.True(number.IsFailure); - Assert.IsType>(number); - Assert.Equal("failure", (number as Failure)!.Exception.Message); - - Assert.False(text.IsSuccess); - Assert.True(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("failure", (text as Failure)!.Exception.Message); + Assert.IsType>(result); + Assert.Equal(FailureException, result.GetExceptionOrThrow()); } - [Fact(DisplayName = "Result.OfAsync with cancellation token should produce expected success result")] + [Fact(DisplayName = "Result.OfAsync with cancellation token should produce the expected success result.")] public async Task ResultOfAsyncWithCancellationTokenShouldProduceExpectedSuccessResult() { // Given / When - CancellationToken token = CancellationToken.None; - Result number = await Result.OfAsync(async _ => await Task.FromResult(123), token); - Result text = await Result.OfAsync(async _ => await Task.FromResult("abc"), token); + Result result = await Result.OfAsync(async () => await Task.FromResult(1), CancellationToken.None); // Then - Assert.True(number.IsSuccess); - Assert.False(number.IsFailure); - Assert.IsType>(number); - Assert.Equal(123, number); - - Assert.True(text.IsSuccess); - Assert.False(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("abc", text); + Assert.IsType>(result); } - [Fact(DisplayName = "Result.OfAsync with cancellation token should produce expected failure result")] + [Fact(DisplayName = "Result.OfAsync with cancellation token should produce the expected failure result.")] public async Task ResultOfAsyncWithCancellationTokenShouldProduceExpectedFailureResult() { // Given / When - Exception exception = new("failure"); - CancellationToken token = CancellationToken.None; - Result number = await Result.OfAsync(_ => throw exception, token); - Result text = await Result.OfAsync(_ => throw exception, token); + Result result = await Result.OfAsync(async () => await Task.FromException(FailureException), CancellationToken.None); // Then - Assert.False(number.IsSuccess); - Assert.True(number.IsFailure); - Assert.IsType>(number); - Assert.Equal("failure", (number as Failure)!.Exception.Message); - - Assert.False(text.IsSuccess); - Assert.True(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("failure", (text as Failure)!.Exception.Message); + Assert.IsType>(result); + Assert.Equal(FailureException, result.GetExceptionOrThrow()); } - [Fact(DisplayName = "Result.Success should produce the expected result")] - public void ResultSuccessShouldProduceExpectedResult() + [Fact(DisplayName = "Result.OfAsync with cancellable function should produce the expected success result.")] + public async Task ResultOfAsyncWithCancellableFunctionShouldProduceExpectedSuccessResult() { // Given / When - Result number = Result.Success(123); - Result text = Result.Success("abc"); + Result result = await Result.OfAsync(async _ => await Task.FromResult(1), CancellationToken.None); // Then - Assert.True(number.IsSuccess); - Assert.False(number.IsFailure); - Assert.IsType>(number); - Assert.Equal(123, number); - - Assert.True(text.IsSuccess); - Assert.False(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("abc", text); + Assert.IsType>(result); } - [Fact(DisplayName = "Result.Failure should produce the expected result")] - public void ResultFailureShouldProduceExpectedResult() + [Fact(DisplayName = "Result.OfAsync with cancellable function should produce the expected failure result.")] + public async Task ResultOfAsyncWithCancellableFunctionShouldProduceExpectedFailureResult() { // Given / When - Exception exception = new("failure"); - Result number = Result.Failure(exception); - Result text = Result.Failure(exception); + Result result = await Result.OfAsync(async _ => await Task.FromException(FailureException), CancellationToken.None); // Then - Assert.False(number.IsSuccess); - Assert.True(number.IsFailure); - Assert.IsType>(number); - Assert.Equal("failure", (number as Failure)!.Exception.Message); - - Assert.False(text.IsSuccess); - Assert.True(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("failure", (text as Failure)!.Exception.Message); + Assert.IsType>(result); + Assert.Equal(FailureException, result.GetExceptionOrThrow()); } - [Fact(DisplayName = "Result implicit operator should produce the expected success result.")] - public void ResultImplicitOperatorShouldProduceTheExpectedSuccessResult() + [Fact(DisplayName = "Result.Success should produce the expected success result.")] + public void ResultSuccessShouldProduceExpectedSuccessResult() { // Given / When - Result number = 123; - Result text = "abc"; + Result result = Result.Success(123); // Then - Assert.True(number.IsSuccess); - Assert.False(number.IsFailure); - Assert.IsType>(number); - Assert.Equal(123, number); - - Assert.True(text.IsSuccess); - Assert.False(text.IsFailure); - Assert.IsType>(text); - Assert.Equal("abc", text); + Assert.IsType>(result); } - [Fact(DisplayName = "Result implicit operator should produce the expected failure result.")] - public void ResultImplicitOperatorShouldProduceTheExpectedFailureResult() + [Fact(DisplayName = "Result.Failure should produce the expected failure result.")] + public void ResultFailureShouldProduceExpectedFailureResult() { // Given / When - Exception exception = new("failure"); - Result result = exception; + Result result = Result.Failure(FailureException); // Then - Assert.False(result.IsSuccess); - Assert.True(result.IsFailure); - Assert.IsType>(result); - Assert.Equal("failure", (result as Failure)!.Exception.Message); + Assert.IsType>(result); + Assert.Equal(FailureException, result.GetExceptionOrThrow()); } - [Fact(DisplayName = "Result Success explicit operator should produce the expected result.")] - public void ResultSuccessExplicitOperatorShouldProduceTheExpectedResult() + [Fact(DisplayName = "Result from implicit value should produce the expected success result.")] + public void ResultFromImplicitValueShouldProduceExpectedSuccessResult() { - // Given - Result number = Result.Success(123); - Result text = Result.Success("abc"); - - // When - int underlyingNumber = (int)number; - string underlyingText = (string)text; + // Given / When + Result result = 1; // Then - Assert.Equal(123, underlyingNumber); - Assert.Equal("abc", underlyingText); + Assert.IsType>(result); + Assert.Equal(1, result.GetValueOrThrow()); } - [Fact(DisplayName = "Result Failure explicit operator should produce the expected result.")] - public void ResultFailureExplicitOperatorShouldProduceTheExpectedResult() + [Fact(DisplayName = "Result from implicit exception should produce the expected failure result.")] + public void ResultFromImplicitExceptionShouldProduceExpectedFailureResult() { - // Given - Exception exception = new("failure"); - Result number = Result.Failure(exception); - Result text = Result.Failure(exception); - - // When - Exception numberException = Assert.Throws(() => (int)number); - Exception textException = Assert.Throws(() => (string)text); + // Given / When + Result result = FailureException; // Then - Assert.Equal("failure", numberException.Message); - Assert.Equal("failure", textException.Message); + Assert.IsType>(result); + Assert.Equal(FailureException, result.GetExceptionOrThrow()); } - [Fact(DisplayName = "Result Success values should be considered equal.")] - public void ResultSuccessValuesShouldBeConsideredEqual() + [Fact(DisplayName = "Result to explicit value from success should produce the expected result.")] + public void ResultToExplicitValueFromSuccessShouldProduceExpectedResult() { // Given - Success a = Result.Success(123); - Success b = Result.Success(123); - - // When / Then - Assert.Equal(a, b); - Assert.True(a.Equals(b)); - Assert.True(a == b); - Assert.False(a != b); - } - - [Fact(DisplayName = "Result Success values should not be considered equal.")] - public void ResultSuccessValuesShouldNotBeConsideredEqual() - { - // Given - Success a = Result.Success(123); - Success b = Result.Success(456); - - // When / Then - Assert.NotEqual(a, b); - Assert.False(a.Equals(b)); - Assert.True(a != b); - Assert.False(a == b); - } - - [Fact(DisplayName = "Result Failure values should be considered equal.")] - public void ResultFailureValuesShouldBeConsideredEqual() - { - // Given - Exception exception = new("failure"); - Failure a = Result.Failure(exception); - Failure b = Result.Failure(exception); - - // When / Then - Assert.Equal(a, b); - Assert.True(a.Equals(b)); - Assert.True(a == b); - Assert.False(a != b); - - // Note that a and b are equal because they share references to the same exception. - } - - [Fact(DisplayName = "Result Failure values should not be considered equal.")] - public void ResultFailureValuesShouldNotBeConsideredEqual() - { - // Given - Exception exception1 = new("failure a"); - Exception exception2 = new("failure b"); - Failure a = Result.Failure(exception1); - Failure b = Result.Failure(exception2); - - // When / Then - Assert.NotEqual(a, b); - Assert.False(a.Equals(b)); - Assert.True(a != b); - Assert.False(a == b); - } - - [Fact(DisplayName = "Result Success and Failure values should not be considered equal.")] - public void ResultSuccessAndFailureValuesShouldNotBeConsideredEqual() - { - // Given - Exception exception = new("failure"); - Result a = Result.Success(123); - Result b = Result.Failure(exception); - - // When / Then - Assert.NotEqual(a, b); - Assert.False(a.Equals(b)); - Assert.True(a != b); - Assert.False(a == b); - } - - [Fact(DisplayName = "Result Success.GetHashCode should produce the expected result.")] - public void ResultSuccessGetHashCodeShouldProduceExpectedResult() - { - // Given - int expected = 123.GetHashCode(); - Result result = Result.Success(123); + Result result = 1; + const int expected = 1; // When - int actual = result.GetHashCode(); + int actual = (int)result; // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.GetHashCode should produce the expected result.")] - public void ResultFailureGetHashCodeShouldProduceExpectedResult() + [Fact(DisplayName = "Result to explicit value from failure should produce the expected result.")] + public void ResultToExplicitValueFromFailureShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - int expected = exception.GetHashCode(); - Result result = Result.Failure(exception); + Result result = Result.Failure(FailureException); // When - int actual = result.GetHashCode(); + Exception exception = Assert.Throws(() => (int)result); // Then - Assert.Equal(expected, actual); + Assert.Equal(FailureException, exception); } - [Fact(DisplayName = "Result Success.GetExceptionOrDefault should produce the expected result.")] - public void ResultSuccessGetExceptionOrDefaultShouldProduceExpectedResult() + [Fact(DisplayName = "Result equality should produce the expected result.")] + public void ResultEqualityShouldProduceExpectedResult() { - // Given - Result result = Result.Success(123); + // ReSharper disable EqualExpressionComparison - // When - Exception? actual = result.GetExceptionOrDefault(); + // Equals Method + Assert.True(Result.Success(1).Equals(Result.Success(1))); + Assert.True(Result.Failure(FailureException).Equals(Result.Failure(FailureException))); + Assert.False(Result.Success(1).Equals(Result.Success(2))); + Assert.False(Result.Success(1).Equals(null)); + Assert.False(Result.Failure(FailureException).Equals(null)); - // Then - Assert.Null(actual); - } + // Equality Operator + Assert.True(Result.Success(1) == Result.Success(1)); + Assert.True(Result.Failure(FailureException) == Result.Failure(FailureException)); + Assert.False(Result.Success(1) == Result.Success(2)); + Assert.False(Result.Success(1) == Result.Failure(FailureException)); + Assert.False(Result.Success(1) == null); + Assert.False(null == Result.Success(1)); - [Fact(DisplayName = "Result Success.GetExceptionOrDefault with default value should produce the expected result.")] - public void ResultSuccessGetExceptionOrDefaultWithDefaultValueShouldProduceExpectedResult() - { - // Given - Exception expected = new("failure"); - Result result = Result.Success(123); - - // When - Exception actual = result.GetExceptionOrDefault(expected); + // Inequality Operator + Assert.True(Result.Success(1) != Result.Failure(FailureException)); + Assert.True(Result.Failure(FailureException) != Result.Failure(new Exception())); + Assert.True(Result.Success(1) != null); + Assert.True(null != Result.Success(1)); - // Then - Assert.Equal(expected, actual); + // Hash Code Generation + Assert.True(Result.Success(1).GetHashCode() == Result.Success(1).GetHashCode()); + Assert.True(Result.Failure(FailureException).GetHashCode() == Result.Failure(FailureException).GetHashCode()); } - [Fact(DisplayName = "Result Success.GetExceptionOrThrow should produce the expected result.")] - public void ResultSuccessGetExceptionOrThrowShouldProduceExpectedResult() + [Fact(DisplayName = "Result.GetExceptionOrDefault from success should produce the expected result.")] + public void ResultGetExceptionOrDefaultFromSuccessShouldProduceExpectedResult() { // Given - Result result = Result.Success(123); + Result result = Result.Success(1); - // When - Exception exception = Assert.Throws(() => result.GetExceptionOrThrow()); + // Then + Exception? exception = result.GetExceptionOrDefault(); // Then - Assert.Equal("The current result is not in a Failure state.", exception.Message); + Assert.Null(exception); } - [Fact(DisplayName = "Result Failure.GetExceptionOrDefault should produce the expected result.")] - public void ResultFailureGetExceptionOrDefaultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.GetExceptionOrDefault from failure should produce the expected result.")] + public void ResultGetExceptionOrDefaultFromFailureShouldProduceExpectedResult() { // Given - Exception expected = new("failure"); - Result result = Result.Failure(expected); + Result result = Result.Failure(FailureException); - // When - Exception? actual = result.GetExceptionOrDefault(); + // Then + Exception? exception = result.GetExceptionOrDefault(); // Then - Assert.Equal(expected, actual); + Assert.Equal(FailureException, exception); } - [Fact(DisplayName = "Result Failure.GetExceptionOrDefault with default value should produce the expected result.")] - public void ResultFailureGetExceptionOrDefaultWithDefaultValueShouldProduceExpectedResult() + [Fact(DisplayName = "Result.GetExceptionOrDefault from success with default value should produce the expected result.")] + public void ResultGetExceptionOrDefaultFromSuccessWithDefaultValueShouldProduceExpectedResult() { // Given - Exception expected = new("failure"); - Result result = Result.Failure(expected); + Result result = Result.Success(1); + Exception defaultValue = new("Default"); - // When - Exception actual = result.GetExceptionOrDefault(new Exception("unexpected exception")); + // Then + Exception exception = result.GetExceptionOrDefault(defaultValue); // Then - Assert.Equal(expected, actual); + Assert.Equal(defaultValue, exception); } - [Fact(DisplayName = "Result Failure.GetExceptionOrThrow should produce the expected result.")] - public void ResultFailureGetExceptionOrThrowShouldProduceExpectedResult() + [Fact(DisplayName = "Result.GetExceptionOrDefault from failure with default value should produce the expected result.")] + public void ResultGetExceptionOrDefaultFromFailureWithDefaultValueShouldProduceExpectedResult() { // Given - Exception expected = new("failure"); - Result result = Result.Failure(expected); + Result result = Result.Failure(FailureException); + Exception defaultValue = new("Default"); - // When - Exception actual = result.GetExceptionOrThrow(); + // Then + Exception exception = result.GetExceptionOrDefault(defaultValue); // Then - Assert.Equal(expected, actual); + Assert.Equal(FailureException, exception); } - [Fact(DisplayName = "Result Success.GetValueOrDefault should produce the expected result.")] - public void ResultSuccessGetValueOrDefaultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.GetExceptionOrThrow from success should produce the expected result.")] + public void ResultGetExceptionOrThrowFromSuccessShouldProduceExpectedResult() { // Given - Result number = Result.Success(123); - Result text = Result.Success("abc"); + Result result = Result.Success(1); - // When - int actualNumber = number.GetValueOrDefault(); - string? actualText = text.GetValueOrDefault(); + // Then + Exception exception = Assert.Throws(() => result.GetExceptionOrThrow()); // Then - Assert.Equal(123, actualNumber); - Assert.Equal("abc", actualText); + Assert.Equal("The current result is not in a failure state.", exception.Message); } - [Fact(DisplayName = "Result Failure.GetValueOrDefault should produce the expected result.")] - public void ResultFailureGetValueOrDefaultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.GetExceptionOrThrow from failure should produce the expected result.")] + public void ResultGetExceptionOrThrowFromFailureShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Result number = Result.Failure(exception); - Result text = Result.Failure(exception); + Result result = Result.Failure(FailureException); - // When - int actualNumber = number.GetValueOrDefault(); - string? actualText = text.GetValueOrDefault(); + // Then + Exception exception = result.GetExceptionOrThrow(); // Then - Assert.Equal(default, actualNumber); - Assert.Equal(default, actualText); + Assert.Equal(FailureException, exception); } - [Fact(DisplayName = "Result Success.GetValueOrDefault with default value should produce the expected result.")] - public void ResultSuccessGetValueOrDefaultWithDefaultValueShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Match action from success should produce the expected result.")] + public void ResultMatchActionFromSuccessShouldProduceExpectedResult() { // Given - Result number = Result.Success(123); - Result text = Result.Success("abc"); + Result result = Result.Success(1); + bool isSuccess = false; + bool isFailure = false; // When - int actualNumber = number.GetValueOrDefault(456); - string? actualText = text.GetValueOrDefault("xyz"); + result.Match( + success: _ => isSuccess = true, + failure: _ => isFailure = true + ); // Then - Assert.Equal(123, actualNumber); - Assert.Equal("abc", actualText); + Assert.True(isSuccess); + Assert.False(isFailure); } - [Fact(DisplayName = "Result Failure.GetValueOrDefault with default value should produce the expected result.")] - public void ResultFailureGetValueOrDefaultWithDefaultValueShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Match action from failure should produce the expected result.")] + public void ResultMatchActionFromFailureShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Result number = Result.Failure(exception); - Result text = Result.Failure(exception); + Result result = Result.Failure(FailureException); + bool isSuccess = false; + bool isFailure = false; // When - int actualNumber = number.GetValueOrDefault(456); - string? actualText = text.GetValueOrDefault("xyz"); + result.Match( + success: _ => isSuccess = true, + failure: _ => isFailure = true + ); // Then - Assert.Equal(456, actualNumber); - Assert.Equal("xyz", actualText); + Assert.False(isSuccess); + Assert.True(isFailure); } - [Fact(DisplayName = "Result Success.GetValueOrThrow should produce the expected result.")] - public void ResultSuccessGetValueOrThrowShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Match function from success should produce the expected result.")] + public void ResultMatchFunctionFromSuccessShouldProduceExpectedResult() { // Given - Result number = Result.Success(123); - Result text = Result.Success("abc"); + Result result = Result.Success(1); + const int expected = 1; // When - int underlyingNumber = number.GetValueOrThrow(); - string underlyingText = text.GetValueOrThrow(); + int actual = result.Match( + success: _ => 1, + failure: _ => 0 + ); // Then - Assert.Equal(123, underlyingNumber); - Assert.Equal("abc", underlyingText); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.GetValueOrThrow should produce the expected result.")] - public void ResultFailureGetValueOrThrowShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Match function from failure should produce the expected result.")] + public void ResultMatchFunctionFromFailureShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Result number = Result.Failure(exception); - Result text = Result.Failure(exception); + Result result = Result.Failure(FailureException); + const int expected = 1; // When - Exception numberException = Assert.Throws(() => number.GetValueOrThrow()); - Exception textException = Assert.Throws(() => text.GetValueOrThrow()); + int actual = result.Match( + success: _ => 0, + failure: _ => 1 + ); // Then - Assert.Equal("failure", numberException.Message); - Assert.Equal("failure", textException.Message); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.Match should execute the some action.")] - public void ResultSuccessMatchShouldExecuteSuccessAction() + [Fact(DisplayName = "Result.Select action from failure should produce the expected result.")] + public void ResultSelectActionFromFailureShouldProduceExpectedResult() { // Given - bool successCalled = false; - Result result = 123; + Result result = Result.Failure(FailureException); + Result expected = Result.Failure(FailureException); - // When - result.Match(success: _ => { successCalled = true; }); + // Then + Result actual = result.Select(_ => { }); // Then - Assert.True(successCalled); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.Match should execute the none action.")] - public void ResultFailureMatchShouldExecuteFailureAction() + [Fact(DisplayName = "Result.Select action from success should produce the expected result.")] + public void ResultSelectActionFromSuccessShouldProduceExpectedResult() { // Given - bool failureCalled = false; - Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Success(1); + Result expected = Result.Success(); - // When - result.Match(failure: _ => { failureCalled = true; }); + // Then + Result actual = result.Select(_ => { }); // Then - Assert.True(failureCalled); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.Match should produce the expected result.")] - public void ResultSuccessMatchShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Select action from success with exception should produce the expected result.")] + public void ResultSelectActionFromSuccessWithExceptionShouldProduceExpectedResult() { // Given - const int expected = 9; - Result result = 3; + Result result = Result.Success(1); + Result expected = Result.Failure(FailureException); - // When - int actual = result.Match( - success: value => value * value, - failure: _ => 0 - ); + // Then + Result actual = result.Select(_ => throw FailureException); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.Match should produce the expected result.")] - public void ResultFailureMatchShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Select function of TResult from failure should produce the expected result.")] + public void ResultSelectFunctionOfTResultFromFailureShouldProduceExpectedResult() { // Given - const int expected = 0; - Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Failure(FailureException); + Result expected = Result.Failure(FailureException); - // When - int actual = result.Match( - success: value => value * value, - failure: _ => 0 - ); + // Then + Result actual = result.Select(_ => 1); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.Select should produce the expected result")] - public void ResultSuccessSelectShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Select function of TResult from success should produce the expected result.")] + public void ResultSelectFunctionOfTResultFromSuccessShouldProduceExpectedResult() { // Given - Result expected = Result.Success(); - Result result = 123; + Result result = Result.Success(2); + Result expected = 1; - // When - Result actual = result.Select(_ => { }); + // Then + Result actual = result.Select(_ => 1); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.Select should produce the expected result")] - public void ResultFailureSelectShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Select function of TResult from success with exception should produce the expected result.")] + public void ResultSelectFunctionOfTResultFromSuccessWithExceptionShouldProduceExpectedResult() { // Given - Exception exception = new("Failure"); - Result expected = Result.Failure(exception); - Result result = Result.Failure(exception); + Result result = Result.Success(1); + Result expected = Result.Failure(FailureException); - // When - Result actual = result.Select(_ => { }); + // Then + Result actual = result.Select(_ => throw FailureException); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.Select should produce the expected result")] - public void ResultSuccessSelectTResultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.SelectMany function from failure should produce the expected result.")] + public void ResultSelectManyFunctionFromFailureShouldProduceExpectedResult() { // Given - Result expected = 9; - Result result = 3; + Result result = Result.Failure(FailureException); + Result expected = Result.Failure(FailureException); // When - Result actual = result.Select(x => x * x); + Result actual = result.SelectMany(_ => Result.Success()); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.Select should produce the expected result")] - public void ResultFailureSelectTResultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.SelectMany function from success should produce the expected result.")] + public void ResultSelectManyFunctionFromSuccessShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Success(1); + Result expected = Result.Success(); // When - Result actual = result.Select(x => x * x); + Result actual = result.SelectMany(_ => Result.Success()); // Then - Assert.Equal(Result.Failure(exception), actual); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.SelectMany should produce the expected result")] - public void ResultSuccessSelectManyShouldProduceExpectedResult() + [Fact(DisplayName = "Result.SelectMany function from success with exception should produce the expected result.")] + public void ResultSelectManyFunctionFromSuccessWithExceptionShouldProduceExpectedResult() { // Given - Result expected = Result.Success(); - Result result = 3; + Result result = Result.Success(1); + Result expected = Result.Failure(FailureException); // When - Result actual = result.SelectMany(_ => Result.Success()); + Result actual = result.SelectMany(_ => throw FailureException); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.SelectMany should produce the expected result")] - public void ResultFailureSelectManyShouldProduceExpectedResult() + [Fact(DisplayName = "Result.SelectMany function of TResult from failure should produce the expected result.")] + public void ResultSelectManyFunctionOfTResultFromFailureShouldProduceExpectedResult() { // Given - Exception exception = new("Failure"); - Result expected = Result.Failure(exception); - Result result = Result.Failure(exception); + Result result = Result.Failure(FailureException); + Result expected = Result.Failure(FailureException); // When - Result actual = result.SelectMany(_ => Result.Success()); + Result actual = result.SelectMany(_ => 1); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.SelectMany should produce the expected result")] - public void ResultSuccessSelectManyTResultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.SelectMany function of TResult from success should produce the expected result.")] + public void ResultSelectManyFunctionOfTResultFromSuccessShouldProduceExpectedResult() { // Given - Result expected = 9; - Result result = 3; + Result result = Result.Success(2); + Result expected = 1; // When - Result actual = result.SelectMany(x => Result.Success(x * x)); + Result actual = result.SelectMany(_ => 1); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.SelectMany should produce the expected result")] - public void ResultFailureSelectManyTResultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.SelectMany function of TResult from success with exception should produce the expected result.")] + public void ResultSelectManyFunctionOfTResultFromSuccessWithExceptionShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Success(1); + Result expected = Result.Failure(FailureException); // When - Result actual = result.SelectMany(x => Result.Success(x * x)); + Result actual = result.SelectMany(_ => throw FailureException); // Then - Assert.Equal(Result.Failure(exception), actual); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.Throw should do nothing")] - public void ResultSuccessThrowShouldDoNothing() + [Fact(DisplayName = "Result.Throw from success should produce expected result.")] + public void ResultThrowFromSuccessShouldProduceExpectedResult() { // Given - Result result = Result.Success(123); + Result result = Result.Success(1); // When / Then result.Throw(); } - [Fact(DisplayName = "Result Failure.Throw should throw Exception")] - public void ResultFailureThrowShouldThrowException() + [Fact(DisplayName = "Result.Throw from failure should produce expected result.")] + public void ResultThrowFromFailureShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Failure(FailureException); - // When / Then - Assert.Throws(() => result.Throw()); - } + // When + Exception exception = Assert.Throws(() => result.Throw()); + // Then + Assert.Equal(FailureException, exception); + } - [Fact(DisplayName = "Result Success.ToString should produce the expected result.")] - public void ResultSuccessToStringShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Dispose from success should produce the expected result.")] + public void ResultDisposeFromSuccessShouldProduceExpectedResult() { // Given - Result number = 123; - Result text = "abc"; + Disposable disposable = new(); + Result result = Result.Success(disposable); // When - string numberString = number.ToString(); - string textString = text.ToString(); + result.Dispose(); // Then - Assert.Equal("123", numberString); - Assert.Equal("abc", textString); + Assert.True(disposable.IsDisposed); } - [Fact(DisplayName = "Result Failure.ToString should produce the expected result.")] - public void ResultFailureToStringShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Dispose from failure should produce the expected result.")] + public void ResultDisposeFromFailureShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Result number = Result.Failure(exception); - Result text = Result.Failure(exception); + Disposable disposable = new(); + Result result = Result.Failure(FailureException); // When - string numberString = number.ToString(); - string textString = text.ToString(); + result.Dispose(); // Then - Assert.Equal("System.Exception: failure", numberString); - Assert.Equal("System.Exception: failure", textString); + Assert.False(disposable.IsDisposed); } - [Fact(DisplayName = "Result Failure.ToTypedResult should produce the expected result.")] - public void ResultFailureToTypedResultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.DisposeAsync from success should produce the expected result.")] + public async Task ResultDisposeAsyncFromSuccessShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Failure result = Result.Failure(exception); + Disposable disposable = new(); + Result result = Result.Success(disposable); // When - Result actual = result.ToTypedResult(); + await result.DisposeAsync(); // Then - Assert.IsType>(actual); - Assert.Equal("System.Exception: failure", actual.GetExceptionOrThrow().ToString()); + Assert.True(disposable.IsDisposed); } - [Fact(DisplayName = "Result Failure.ToUntypedResult should produce the expected result.")] - public void ResultFailureToUntypedResultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.DisposeAsync from failure should produce the expected result.")] + public async Task ResultDisposeAsyncFromFailureShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Failure result = Result.Failure(exception); + Disposable disposable = new(); + Result result = Result.Failure(FailureException); // When - Result actual = result.ToUntypedResult(); + await result.DisposeAsync(); // Then - Assert.IsType(actual); - Assert.Equal("System.Exception: failure", actual.GetExceptionOrThrow().ToString()); + Assert.False(disposable.IsDisposed); } - [Fact(DisplayName = "Result Success.Dispose should dispose of the underlying value.")] - public void ResultSuccessDisposeShouldDisposeUnderlyingValue() + [Fact(DisplayName = "Result.ToString from success should produce expected result.")] + public void ResultToStringFromSuccessShouldProduceExpectedResult() { // Given - Disposable disposable = new(); - Success result = Result.Success(disposable); + Result result = Result.Success(1); + const string expected = "1"; // When - result.Dispose(); + string actual = result.ToString(); // Then - Assert.True(disposable.IsDisposed); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.Dispose should do nothing.")] - public void ResultFailureDisposeShouldDoNothing() + [Fact(DisplayName = "Result.ToString from failure should produce expected result.")] + public void ResultToStringFromFailureShouldProduceExpectedResult() { // Given - Disposable disposable = new(); - Exception exception = new("failure"); - Failure result = Result.Failure(exception); + Result result = Result.Failure(FailureException); + string expected = FailureException.Message; // When - result.Dispose(); + string actual = result.ToString(); // Then - Assert.False(disposable.IsDisposed); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.DisposeAsync should dispose of the underlying value.")] - public async Task ResultSuccessDisposeAsyncShouldDisposeUnderlyingValue() + [Fact(DisplayName = "Failure.ToTypedResult should produce the expected result.")] + public void FailureToTypedResultShouldProduceExpectedResult() { // Given - Disposable disposable = new(); - Success result = Result.Success(disposable); + Failure result = Result.Failure(FailureException); + Result expected = FailureException; // When - await result.DisposeAsync(); + Result actual = result.ToTypedResult(); // Then - Assert.True(disposable.IsDisposed); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.DisposeAsync should do nothing.")] - public async Task ResultFailureDisposeAsyncShouldDoNothing() + [Fact(DisplayName = "Failure.ToUntypedResult should produce the expected result.")] + public void FailureToUntypedResultShouldProduceExpectedResult() { // Given - Disposable disposable = new(); - Exception exception = new("failure"); - Failure result = Result.Failure(exception); + Failure result = Result.Failure(FailureException); + Result expected = FailureException; // When - await result.DisposeAsync(); + Result actual = result.ToUntypedResult(); // Then - Assert.False(disposable.IsDisposed); + Assert.Equal(expected, actual); } } diff --git a/OnixLabs.Core.UnitTests/ResultTests.cs b/OnixLabs.Core.UnitTests/ResultTests.cs index 1adad8c..a6acdce 100644 --- a/OnixLabs.Core.UnitTests/ResultTests.cs +++ b/OnixLabs.Core.UnitTests/ResultTests.cs @@ -21,471 +21,497 @@ namespace OnixLabs.Core.UnitTests; public sealed class ResultTests { - [Fact(DisplayName = "Result.Of should produce expected success result")] + private static readonly Exception FailureException = new("Failure"); + + [Fact(DisplayName = "Result.IsSuccess should produce the expected result")] + public void ResultIsSuccessShouldProduceExpectedResult() + { + // Given + Result result = Result.Success(); + + // When / Then + Assert.True(result.IsSuccess); + Assert.False(result.IsFailure); + } + + [Fact(DisplayName = "Result.IsFailure should produce the expected result")] + public void ResultIsFailureShouldProduceExpectedResult() + { + // Given + Result result = Result.Failure(FailureException); + + // When / Then + Assert.True(result.IsFailure); + Assert.False(result.IsSuccess); + } + + [Fact(DisplayName = "Result.Of should produce the expected success result.")] public void ResultOfShouldProduceExpectedSuccessResult() { // Given / When Result result = Result.Of(() => { }); // Then - Assert.True(result.IsSuccess); - Assert.False(result.IsFailure); Assert.IsType(result); } - [Fact(DisplayName = "Result.Of should produce expected failure result")] + [Fact(DisplayName = "Result.Of should produce the expected failure result.")] public void ResultOfShouldProduceExpectedFailureResult() { // Given / When - Exception exception = new("failure"); - Result result = Result.Of(() => throw exception); + Result result = Result.Of(() => throw FailureException); // Then - Assert.False(result.IsSuccess); - Assert.True(result.IsFailure); Assert.IsType(result); + Assert.Equal(FailureException, result.GetExceptionOrThrow()); } - [Fact(DisplayName = "Result.OfAsync should produce expected success result")] + [Fact(DisplayName = "Result.OfAsync should produce the expected success result.")] public async Task ResultOfAsyncShouldProduceExpectedSuccessResult() { // Given / When Result result = await Result.OfAsync(async () => await Task.CompletedTask); // Then - Assert.True(result.IsSuccess); - Assert.False(result.IsFailure); Assert.IsType(result); } - [Fact(DisplayName = "Result.OfAsync should produce expected failure result")] + [Fact(DisplayName = "Result.OfAsync should produce the expected failure result.")] public async Task ResultOfAsyncShouldProduceExpectedFailureResult() { // Given / When - Exception exception = new("failure"); - Result result = await Result.OfAsync(() => throw exception); + Result result = await Result.OfAsync(async () => await Task.FromException(FailureException)); // Then - Assert.False(result.IsSuccess); - Assert.True(result.IsFailure); Assert.IsType(result); + Assert.Equal(FailureException, result.GetExceptionOrThrow()); } - [Fact(DisplayName = "Result.OfAsync with cancellation token should produce expected success result")] + [Fact(DisplayName = "Result.OfAsync with cancellation token should produce the expected success result.")] public async Task ResultOfAsyncWithCancellationTokenShouldProduceExpectedSuccessResult() { // Given / When - CancellationToken token = CancellationToken.None; - Result result = await Result.OfAsync(async _ => await Task.CompletedTask, token); + Result result = await Result.OfAsync(async () => await Task.CompletedTask, CancellationToken.None); // Then - Assert.True(result.IsSuccess); - Assert.False(result.IsFailure); Assert.IsType(result); } - [Fact(DisplayName = "Result.OfAsync with cancellation token should produce expected failure result")] + [Fact(DisplayName = "Result.OfAsync with cancellation token should produce the expected failure result.")] public async Task ResultOfAsyncWithCancellationTokenShouldProduceExpectedFailureResult() { // Given / When - Exception exception = new("failure"); - CancellationToken token = CancellationToken.None; - Result result = await Result.OfAsync(_ => throw exception, token); + Result result = await Result.OfAsync(async () => await Task.FromException(FailureException), CancellationToken.None); // Then - Assert.False(result.IsSuccess); - Assert.True(result.IsFailure); Assert.IsType(result); + Assert.Equal(FailureException, result.GetExceptionOrThrow()); } - [Fact(DisplayName = "Result.Success should produce the expected result")] - public void ResultSuccessShouldProduceExpectedResult() + [Fact(DisplayName = "Result.OfAsync with cancellable function should produce the expected success result.")] + public async Task ResultOfAsyncWithCancellableFunctionShouldProduceExpectedSuccessResult() { // Given / When - Result result = Result.Success(); + Result result = await Result.OfAsync(async _ => await Task.CompletedTask, CancellationToken.None); // Then - Assert.True(result.IsSuccess); - Assert.False(result.IsFailure); Assert.IsType(result); } - [Fact(DisplayName = "Result.Failure should produce the expected result")] - public void ResultFailureShouldProduceExpectedResult() + [Fact(DisplayName = "Result.OfAsync with cancellable function should produce the expected failure result.")] + public async Task ResultOfAsyncWithCancellableFunctionShouldProduceExpectedFailureResult() { // Given / When - Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = await Result.OfAsync(async _ => await Task.FromException(FailureException), CancellationToken.None); // Then - Assert.False(result.IsSuccess); - Assert.True(result.IsFailure); Assert.IsType(result); + Assert.Equal(FailureException, result.GetExceptionOrThrow()); } - [Fact(DisplayName = "Result implicit operator should produce the expected failure result.")] - public void ResultImplicitOperatorShouldProduceTheExpectedFailureResult() + [Fact(DisplayName = "Result.Success should produce the expected success result.")] + public void ResultSuccessShouldProduceExpectedSuccessResult() { // Given / When - Exception exception = new("failure"); - Result result = exception; + Result result = Result.Success(); + + // Then + Assert.IsType(result); + } + + [Fact(DisplayName = "Result.Failure should produce the expected failure result.")] + public void ResultFailureShouldProduceExpectedFailureResult() + { + // Given / When + Result result = Result.Failure(FailureException); // Then - Assert.False(result.IsSuccess); - Assert.True(result.IsFailure); Assert.IsType(result); - Assert.Equal("failure", (result as Failure)!.Exception.Message); + Assert.Equal(FailureException, result.GetExceptionOrThrow()); } - [Fact(DisplayName = "Result Success values should be considered equal.")] - public void ResultSuccessValuesShouldBeConsideredEqual() + [Fact(DisplayName = "Result from implicit exception should produce the expected failure result.")] + public void ResultFromImplicitExceptionShouldProduceExpectedFailureResult() { - // Given - Success a = Result.Success(); - Success b = Success.Instance; + // Given / When + Result result = FailureException; - // When / Then - Assert.Equal(a, b); - Assert.True(a.Equals(b)); - Assert.True(a == b); - Assert.False(a != b); - Assert.True((Result)a == (Result)b); - Assert.False((Result)a != (Result)b); + // Then + Assert.IsType(result); + Assert.Equal(FailureException, result.GetExceptionOrThrow()); } - [Fact(DisplayName = "Result Failure values should be considered equal.")] - public void ResultFailureValuesShouldBeConsideredEqual() + [Fact(DisplayName = "Result equality should produce the expected result.")] + public void ResultEqualityShouldProduceExpectedResult() { - // Given - Exception exception = new("failure"); - Failure a = Result.Failure(exception); - Failure b = exception; + // ReSharper disable EqualExpressionComparison - // When / Then - Assert.Equal(a, b); - Assert.True(a.Equals(b)); - Assert.True(a == b); - Assert.False(a != b); - Assert.True((Result)a == (Result)b); - Assert.False((Result)a != (Result)b); + // Equals Method + Assert.True(Result.Success().Equals(Result.Success())); + Assert.True(Result.Failure(FailureException).Equals(Result.Failure(FailureException))); + Assert.False(Result.Success().Equals(null)); + Assert.False(Result.Failure(FailureException).Equals(null)); + + // Equality Operator + Assert.True(Result.Success() == Result.Success()); + Assert.True(Result.Failure(FailureException) == Result.Failure(FailureException)); + Assert.False(Result.Success() == Result.Failure(FailureException)); + Assert.False(Result.Success() == null); + Assert.False(null == Result.Success()); + + // Inequality Operator + Assert.True(Result.Success() != Result.Failure(FailureException)); + Assert.True(Result.Failure(FailureException) != Result.Failure(new Exception())); + Assert.True(Result.Success() != null); + Assert.True(null != Result.Success()); - // Note that a and b are equal because they share references to the same exception. + // Hash Code Generation + Assert.True(Result.Success().GetHashCode() == Result.Success().GetHashCode()); + Assert.True(Result.Failure(FailureException).GetHashCode() == Result.Failure(FailureException).GetHashCode()); } - [Fact(DisplayName = "Result Failure values should not be considered equal.")] - public void ResultFailureValuesShouldNotBeConsideredEqual() + [Fact(DisplayName = "Result.GetExceptionOrDefault from success should produce the expected result.")] + public void ResultGetExceptionOrDefaultFromSuccessShouldProduceExpectedResult() { // Given - Exception exception1 = new("failure a"); - Exception exception2 = new("failure b"); - Result a = Result.Failure(exception1); - Result b = Result.Failure(exception2); + Result result = Result.Success(); - // When / Then - Assert.NotEqual(a, b); - Assert.True(a != b); - Assert.False(a.Equals(b)); + // Then + Exception? exception = result.GetExceptionOrDefault(); + + // Then + Assert.Null(exception); } - [Fact(DisplayName = "Result Success and Failure values should not be considered equal.")] - public void ResultSuccessAndFailureValuesShouldNotBeConsideredEqual() + [Fact(DisplayName = "Result.GetExceptionOrDefault from failure should produce the expected result.")] + public void ResultGetExceptionOrDefaultFromFailureShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Result a = Result.Success(); - Result b = Result.Failure(exception); + Result result = Result.Failure(FailureException); - // When / Then - Assert.NotEqual(a, b); - Assert.True(a != b); - Assert.False(a.Equals(b)); + // Then + Exception? exception = result.GetExceptionOrDefault(); + + // Then + Assert.Equal(FailureException, exception); } - [Fact(DisplayName = "Result Success.GetHashCode should produce the expected result.")] - public void ResultSuccessGetHashCodeShouldProduceExpectedResult() + [Fact(DisplayName = "Result.GetExceptionOrDefault from success with default value should produce the expected result.")] + public void ResultGetExceptionOrDefaultFromSuccessWithDefaultValueShouldProduceExpectedResult() { // Given - const int expected = 0; Result result = Result.Success(); + Exception defaultValue = new("Default"); - // When - int actual = result.GetHashCode(); + // Then + Exception exception = result.GetExceptionOrDefault(defaultValue); // Then - Assert.Equal(expected, actual); + Assert.Equal(defaultValue, exception); } - [Fact(DisplayName = "Result Failure.GetHashCode should produce the expected result.")] - public void ResultFailureGetHashCodeShouldProduceExpectedResult() + [Fact(DisplayName = "Result.GetExceptionOrDefault from failure with default value should produce the expected result.")] + public void ResultGetExceptionOrDefaultFromFailureWithDefaultValueShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - int expected = exception.GetHashCode(); - Result result = Result.Failure(exception); + Result result = Result.Failure(FailureException); + Exception defaultValue = new("Default"); - // When - int actual = result.GetHashCode(); + // Then + Exception exception = result.GetExceptionOrDefault(defaultValue); // Then - Assert.Equal(expected, actual); + Assert.Equal(FailureException, exception); } - [Fact(DisplayName = "Result Success.GetExceptionOrDefault should produce the expected result.")] - public void ResultSuccessGetExceptionOrDefaultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.GetExceptionOrThrow from success should produce the expected result.")] + public void ResultGetExceptionOrThrowFromSuccessShouldProduceExpectedResult() { // Given Result result = Result.Success(); - // When - Exception? actual = result.GetExceptionOrDefault(); + // Then + Exception exception = Assert.Throws(() => result.GetExceptionOrThrow()); // Then - Assert.Null(actual); + Assert.Equal("The current result is not in a failure state.", exception.Message); } - [Fact(DisplayName = "Result Success.GetExceptionOrDefault with default value should produce the expected result.")] - public void ResultSuccessGetExceptionOrDefaultWithDefaultValueShouldProduceExpectedResult() + [Fact(DisplayName = "Result.GetExceptionOrThrow from failure should produce the expected result.")] + public void ResultGetExceptionOrThrowFromFailureShouldProduceExpectedResult() { // Given - Exception expected = new("failure"); - Result result = Result.Success(); + Result result = Result.Failure(FailureException); - // When - Exception actual = result.GetExceptionOrDefault(expected); + // Then + Exception exception = result.GetExceptionOrThrow(); // Then - Assert.Equal(expected, actual); + Assert.Equal(FailureException, exception); } - [Fact(DisplayName = "Result Success.GetExceptionOrThrow with default value should produce the expected result.")] - public void ResultSuccessGetExceptionOrThrowShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Match action from success should produce the expected result.")] + public void ResultMatchActionFromSuccessShouldProduceExpectedResult() { // Given Result result = Result.Success(); + bool isSuccess = false; + bool isFailure = false; // When - Exception exception = Assert.Throws(() => result.GetExceptionOrThrow()); + result.Match( + success: () => isSuccess = true, + failure: _ => isFailure = true + ); // Then - Assert.Equal("The current result is not in a Failure state.", exception.Message); + Assert.True(isSuccess); + Assert.False(isFailure); } - [Fact(DisplayName = "Result Failure.GetExceptionOrDefault should produce the expected result.")] - public void ResultFailureGetExceptionOrDefaultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Match action from failure should produce the expected result.")] + public void ResultMatchActionFromFailureShouldProduceExpectedResult() { // Given - Exception expected = new("failure"); - Result result = Result.Failure(expected); + Result result = Result.Failure(FailureException); + bool isSuccess = false; + bool isFailure = false; // When - Exception? actual = result.GetExceptionOrDefault(); + result.Match( + success: () => isSuccess = true, + failure: _ => isFailure = true + ); // Then - Assert.Equal(expected, actual); + Assert.False(isSuccess); + Assert.True(isFailure); } - [Fact(DisplayName = "Result Failure.GetExceptionOrDefault with default value should produce the expected result.")] - public void ResultFailureGetExceptionOrDefaultWithDefaultValueShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Match function from success should produce the expected result.")] + public void ResultMatchFunctionFromSuccessShouldProduceExpectedResult() { // Given - Exception expected = new("failure"); - Result result = Result.Failure(expected); + Result result = Result.Success(); + const int expected = 1; // When - Exception actual = result.GetExceptionOrDefault(new Exception("unexpected exception")); + int actual = result.Match( + success: () => 1, + failure: _ => 0 + ); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.GetExceptionOrThrow with default value should produce the expected result.")] - public void ResultFailureGetExceptionOrThrowShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Match function from failure should produce the expected result.")] + public void ResultMatchFunctionFromFailureShouldProduceExpectedResult() { // Given - Exception expected = new("failure"); - Result result = Result.Failure(expected); + Result result = Result.Failure(FailureException); + const int expected = 1; // When - Exception actual = result.GetExceptionOrThrow(); + int actual = result.Match( + success: () => 0, + failure: _ => 1 + ); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.Match should execute the some action.")] - public void ResultSuccessMatchShouldExecuteSuccessAction() + [Fact(DisplayName = "Result.Select action from failure should produce the expected result.")] + public void ResultSelectActionFromFailureShouldProduceExpectedResult() { // Given - bool successCalled = false; - Result result = Result.Success(); + Result result = Result.Failure(FailureException); + Result expected = Result.Failure(FailureException); - // When - result.Match(success: () => { successCalled = true; }); + // Then + Result actual = result.Select(() => { }); // Then - Assert.True(successCalled); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.Match should execute the none action.")] - public void ResultFailureMatchShouldExecuteFailureAction() + [Fact(DisplayName = "Result.Select action from success should produce the expected result.")] + public void ResultSelectActionFromSuccessShouldProduceExpectedResult() { // Given - bool failureCalled = false; - Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Success(); + Result expected = Result.Success(); - // When - result.Match(failure: _ => { failureCalled = true; }); + // Then + Result actual = result.Select(() => { }); // Then - Assert.True(failureCalled); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.Match should produce the expected result.")] - public void ResultSuccessMatchShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Select action from success with exception should produce the expected result.")] + public void ResultSelectActionFromSuccessWithExceptionShouldProduceExpectedResult() { // Given - const int expected = 9; Result result = Result.Success(); + Result expected = Result.Failure(FailureException); - // When - int actual = result.Match( - success: () => 9, - failure: _ => 0 - ); + // Then + Result actual = result.Select(() => throw FailureException); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.Match should produce the expected result.")] - public void ResultFailureMatchShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Select function of TResult from failure should produce the expected result.")] + public void ResultSelectFunctionOfTResultFromFailureShouldProduceExpectedResult() { // Given - const int expected = 0; - Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Failure(FailureException); + Result expected = Result.Failure(FailureException); - // When - int actual = result.Match( - success: () => 9, - failure: _ => 0 - ); + // Then + Result actual = result.Select(() => 1); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.Select should produce the expected result")] - public void ResultSuccessSelectShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Select function of TResult from success should produce the expected result.")] + public void ResultSelectFunctionOfTResultFromSuccessShouldProduceExpectedResult() { // Given - Result expected = Result.Success(); + Result result = Result.Success(); + Result expected = 1; - // When - Result actual = expected.Select(() => { }); + // Then + Result actual = result.Select(() => 1); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.Select should produce the expected result")] - public void ResultFailureSelectShouldProduceExpectedResult() + [Fact(DisplayName = "Result.Select function of TResult from success with exception should produce the expected result.")] + public void ResultSelectFunctionOfTResultFromSuccessWithExceptionShouldProduceExpectedResult() { // Given - Result expected = Result.Failure(new Exception("Failure")); + Result result = Result.Success(); + Result expected = Result.Failure(FailureException); - // When - Result actual = expected.Select(() => { }); + // Then + Result actual = result.Select(() => throw FailureException); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.Select should produce the expected result")] - public void ResultSuccessSelectTResultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.SelectMany function from failure should produce the expected result.")] + public void ResultSelectManyFunctionFromFailureShouldProduceExpectedResult() { // Given - const int expected = 9; - Result result = Result.Success(); + Result result = Result.Failure(FailureException); + Result expected = Result.Failure(FailureException); // When - Result actual = result.Select(() => 9); + Result actual = result.SelectMany(Result.Success); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.Select should produce the expected result")] - public void ResultFailureSelectTResultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.SelectMany function from success should produce the expected result.")] + public void ResultSelectManyFunctionFromSuccessShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Success(); + Result expected = Result.Success(); // When - Result actual = result.Select(() => 9); + Result actual = result.SelectMany(Result.Success); // Then - Assert.Equal(Result.Failure(exception), actual); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.SelectMany should produce the expected result")] - public void ResultSuccessSelectManyShouldProduceExpectedResult() + [Fact(DisplayName = "Result.SelectMany function from success with exception should produce the expected result.")] + public void ResultSelectManyFunctionFromSuccessWithExceptionShouldProduceExpectedResult() { // Given - Result expected = Result.Success(); + Result result = Result.Success(); + Result expected = Result.Failure(FailureException); // When - Result actual = expected.SelectMany(Result.Success); + Result actual = result.SelectMany(() => throw FailureException); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.SelectMany should produce the expected result")] - public void ResultFailureSelectManyShouldProduceExpectedResult() + [Fact(DisplayName = "Result.SelectMany function of TResult from failure should produce the expected result.")] + public void ResultSelectManyFunctionOfTResultFromFailureShouldProduceExpectedResult() { // Given - Result expected = Result.Failure(new Exception("Failure")); + Result result = Result.Failure(FailureException); + Result expected = Result.Failure(FailureException); // When - Result actual = expected.SelectMany(Result.Success); + Result actual = result.SelectMany(() => 1); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.SelectMany should produce the expected result")] - public void ResultSuccessSelectManyTResultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.SelectMany function of TResult from success should produce the expected result.")] + public void ResultSelectManyFunctionOfTResultFromSuccessShouldProduceExpectedResult() { // Given - const int expected = 9; Result result = Result.Success(); + Result expected = 1; // When - Result actual = result.SelectMany(() => 9); + Result actual = result.SelectMany(() => 1); // Then Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.SelectMany should produce the expected result")] - public void ResultFailureSelectManyTResultShouldProduceExpectedResult() + [Fact(DisplayName = "Result.SelectMany function of TResult from success with exception should produce the expected result.")] + public void ResultSelectManyFunctionOfTResultFromSuccessWithExceptionShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Success(); + Result expected = Result.Failure(FailureException); // When - Result actual = result.SelectMany(() => 9); + Result actual = result.SelectMany(() => throw FailureException); // Then - Assert.Equal(Result.Failure(exception), actual); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Success.Throw should do nothing")] - public void ResultSuccessThrowShouldDoNothing() + [Fact(DisplayName = "Result.Throw from success should produce expected result.")] + public void ResultThrowFromSuccessShouldProduceExpectedResult() { // Given Result result = Result.Success(); @@ -494,56 +520,72 @@ public void ResultSuccessThrowShouldDoNothing() result.Throw(); } - [Fact(DisplayName = "Result Failure.Throw should throw Exception")] - public void ResultFailureThrowShouldThrowException() + [Fact(DisplayName = "Result.Throw from failure should produce expected result.")] + public void ResultThrowFromFailureShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Failure(FailureException); - // When / Then - Assert.Throws(() => result.Throw()); + // When + Exception exception = Assert.Throws(() => result.Throw()); + + // Then + Assert.Equal(FailureException, exception); } - [Fact(DisplayName = "Result Success.ToString should produce the expected result.")] - public void ResultSuccessToStringShouldProduceExpectedResult() + [Fact(DisplayName = "Result.ToString from success should produce expected result.")] + public void ResultToStringFromSuccessShouldProduceExpectedResult() { // Given Result result = Result.Success(); + string expected = string.Empty; // When - string resultString = result.ToString(); + string actual = result.ToString(); // Then - Assert.Equal(string.Empty, resultString); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.ToString should produce the expected result.")] - public void ResultFailureToStringShouldProduceExpectedResult() + [Fact(DisplayName = "Result.ToString from failure should produce expected result.")] + public void ResultToStringFromFailureShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Result result = Result.Failure(exception); + Result result = Result.Failure(FailureException); + string expected = FailureException.Message; // When - string resultString = result.ToString(); + string actual = result.ToString(); // Then - Assert.Equal("System.Exception: failure", resultString); + Assert.Equal(expected, actual); } - [Fact(DisplayName = "Result Failure.ToTypedResult should produce the expected result.")] - public void ResultFailureToTypedResultShouldProduceExpectedResult() + [Fact(DisplayName = "Success.ToTypedResult should produce the expected result.")] + public void SuccessToTypedResultShouldProduceExpectedResult() { // Given - Exception exception = new("failure"); - Failure result = Result.Failure(exception); + Success result = Result.Success(); + Result expected = 1; + + // When + Result actual = result.ToTypedResult(1); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Failure.ToTypedResult should produce the expected result.")] + public void FailureToTypedResultShouldProduceExpectedResult() + { + // Given + Failure result = Result.Failure(FailureException); + Result expected = FailureException; // When Result actual = result.ToTypedResult(); // Then - Assert.IsType>(actual); - Assert.Equal("System.Exception: failure", actual.GetExceptionOrThrow().ToString()); + Assert.Equal(expected, actual); } } diff --git a/OnixLabs.Core.UnitTests/Text/StringBuilderExtensionTests.cs b/OnixLabs.Core.UnitTests/Text/StringBuilderExtensionTests.cs index 868765d..2414324 100644 --- a/OnixLabs.Core.UnitTests/Text/StringBuilderExtensionTests.cs +++ b/OnixLabs.Core.UnitTests/Text/StringBuilderExtensionTests.cs @@ -33,6 +33,19 @@ public void StringBuilderAppendShouldProduceTheExpectedResult() Assert.Equal("ABCXYZ123False", builder.ToString()); } + [Fact(DisplayName = "StringBuilder.Prepend of a single item should produce the expected result")] + public void StringBuilderPrependOfSingleItemShouldProduceTheExpectedResult() + { + // Given + StringBuilder builder = new("ABC"); + + // When + builder.Prepend("XYZ"); + + // Then + Assert.Equal("XYZABC", builder.ToString()); + } + [Fact(DisplayName = "StringBuilder.Prepend should produce the expected result")] public void StringBuilderPrependShouldProduceTheExpectedResult() { @@ -46,6 +59,19 @@ public void StringBuilderPrependShouldProduceTheExpectedResult() Assert.Equal("XYZ123FalseABC", builder.ToString()); } + [Fact(DisplayName = "StringBuilder.PrependJoin should produce the expected result")] + public void StringBuilderPrependJoinShouldProduceTheExpectedResult() + { + // Given + StringBuilder builder = new("ABC"); + + // When + builder.PrependJoin(", ", "XYZ", 123, false); + + // Then + Assert.Equal("XYZ, 123, FalseABC", builder.ToString()); + } + [Fact(DisplayName = "StringBuilder.Trim should produce the expected result (char)")] public void StringBuilderTrimShouldProduceTheExpectedResultChar() { diff --git a/OnixLabs.Core/Collections/Generic/Extensions.IEqualityComparer.cs b/OnixLabs.Core/Collections/Generic/Extensions.IEqualityComparer.cs new file mode 100644 index 0000000..7e57701 --- /dev/null +++ b/OnixLabs.Core/Collections/Generic/Extensions.IEqualityComparer.cs @@ -0,0 +1,34 @@ +// 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.Collections.Generic; +using System.ComponentModel; + +namespace OnixLabs.Core.Collections.Generic; + +/// +/// Provides LINQ-like extension methods for . +/// +// ReSharper disable InconsistentNaming +[EditorBrowsable(EditorBrowsableState.Never)] +public static class IEqualityComparerExtensions +{ + /// + /// Gets the current , or the default comparer if the current comparer is . + /// + /// The current . + /// The underlying type of the current . + /// Returns the current , or the default comparer if the current comparer is . + public static IEqualityComparer GetOrDefault(this IEqualityComparer? comparer) => comparer ?? EqualityComparer.Default; +} diff --git a/OnixLabs.Core/Enumeration.Comparable.cs b/OnixLabs.Core/Enumeration.Comparable.cs index ed87b07..45847ba 100644 --- a/OnixLabs.Core/Enumeration.Comparable.cs +++ b/OnixLabs.Core/Enumeration.Comparable.cs @@ -23,7 +23,7 @@ public abstract partial class Enumeration /// /// An object to compare with the current instance. /// Returns a value that indicates the relative order of the objects being compared. - public int CompareTo(T? other) => Value.CompareTo(other?.Value); + public int CompareTo(T? other) => Value.CompareToNullable(other?.Value); /// /// Compares the current instance with another object of the same type and returns an integer that indicates diff --git a/OnixLabs.Core/Enumeration.Equatable.cs b/OnixLabs.Core/Enumeration.Equatable.cs index 0579d5f..b88006f 100644 --- a/OnixLabs.Core/Enumeration.Equatable.cs +++ b/OnixLabs.Core/Enumeration.Equatable.cs @@ -23,14 +23,12 @@ public abstract partial class Enumeration /// /// An object to compare with the current object. /// Returns if the current object is equal to the other parameter; otherwise, . - public bool Equals(T? other) - { - return ReferenceEquals(this, other) - || other is not null - && other.GetType() == GetType() - && other.Value == Value - && other.Name == Name; - } + public bool Equals(T? other) => + ReferenceEquals(this, other) + || other is not null + && other.GetType() == GetType() + && other.Value == Value + && other.Name == Name; /// /// Checks for equality between the current instance and another object. diff --git a/OnixLabs.Core/Enumeration.From.cs b/OnixLabs.Core/Enumeration.From.cs index 467e18c..4f936dc 100644 --- a/OnixLabs.Core/Enumeration.From.cs +++ b/OnixLabs.Core/Enumeration.From.cs @@ -19,6 +19,7 @@ namespace OnixLabs.Core; +// ReSharper disable HeapView.ObjectAllocation, HeapView.DelegateAllocation, HeapView.ClosureAllocation public abstract partial class Enumeration { /// diff --git a/OnixLabs.Core/Enumeration.Get.cs b/OnixLabs.Core/Enumeration.Get.cs index b9b526a..56d84f7 100644 --- a/OnixLabs.Core/Enumeration.Get.cs +++ b/OnixLabs.Core/Enumeration.Get.cs @@ -25,14 +25,11 @@ public abstract partial class Enumeration /// Gets all of the enumeration entries for the current type. /// /// Returns all of the enumeration entries for the current type. - public static IReadOnlySet GetAll() - { - return typeof(T) - .GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) - .Select(field => field.GetValue(null)) - .OfType() - .ToFrozenSet(); - } + public static IReadOnlySet GetAll() => typeof(T) + .GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) + .Select(field => field.GetValue(null)) + .OfType() + .ToFrozenSet(); /// /// Gets all of the enumeration entries for the current type. diff --git a/OnixLabs.Core/Extensions.Array.cs b/OnixLabs.Core/Extensions.Array.cs index af41459..39b0651 100644 --- a/OnixLabs.Core/Extensions.Array.cs +++ b/OnixLabs.Core/Extensions.Array.cs @@ -13,12 +13,14 @@ // limitations under the License. using System.ComponentModel; +using System.Text; namespace OnixLabs.Core; /// /// Provides extension methods for arrays. /// +// ReSharper disable HeapView.ObjectAllocation [EditorBrowsable(EditorBrowsableState.Never)] public static class ArrayExtensions { @@ -48,4 +50,12 @@ public static class ArrayExtensions /// The underlying type of the . /// Returns the current concatenated with the other . public static T[] ConcatenateWith(this T[] array, T[] other) => [..array, ..other]; + + /// + /// Obtains the representation of the current . + /// + /// The instance from which to obtain a representation. + /// The which will be used to convert the current into a representation. + /// Returns the representation of the current . + public static string ToString(this byte[] array, Encoding encoding) => encoding.GetString(array); } diff --git a/OnixLabs.Core/Extensions.Object.cs b/OnixLabs.Core/Extensions.Object.cs index 51661e3..161f541 100644 --- a/OnixLabs.Core/Extensions.Object.cs +++ b/OnixLabs.Core/Extensions.Object.cs @@ -13,11 +13,14 @@ // limitations under the License. using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Reflection; +using System.Text; +using OnixLabs.Core.Linq; using OnixLabs.Core.Reflection; +using OnixLabs.Core.Text; namespace OnixLabs.Core; @@ -27,6 +30,13 @@ namespace OnixLabs.Core; [EditorBrowsable(EditorBrowsableState.Never)] public static class ObjectExtensions { + private const string Null = "null"; + private const string ObjectOpenBracket = " { "; + private const string ObjectCloseBracket = " }"; + private const string ObjectEmptyBrackets = " { }"; + private const string ObjectPropertySeparator = ", "; + private const string ObjectPropertyAssignment = " = "; + /// /// Determines whether the current value falls within range, inclusive of the specified minimum and maximum values. /// @@ -38,10 +48,8 @@ public static class ObjectExtensions /// Returns if the current value falls within range, /// inclusive of the specified minimum and maximum values; otherwise, . /// - public static bool IsWithinRangeInclusive(this T value, T min, T max) where T : IComparable - { - return value.CompareTo(min) is 0 or 1 && value.CompareTo(max) is 0 or -1; - } + public static bool IsWithinRangeInclusive(this T value, T min, T max) where T : IComparable => + value.CompareTo(min) is 0 or 1 && value.CompareTo(max) is 0 or -1; /// /// Determines whether the current value falls within range, exclusive of the specified minimum and maximum values. @@ -54,39 +62,112 @@ public static bool IsWithinRangeInclusive(this T value, T min, T max) where T /// Returns if the current value falls within range, /// exclusive of the specified minimum and maximum values; otherwise, . /// - public static bool IsWithinRangeExclusive(this T value, T min, T max) where T : IComparable + public static bool IsWithinRangeExclusive(this T value, T min, T max) where T : IComparable => + value.CompareTo(min) is 1 && value.CompareTo(max) is -1; + + /// + /// Compares the current instance with the specified instance. + /// + /// The left-hand instance to compare. + /// The right-hand instance to compare. + /// The underlying type of the current . + /// Returns a signed integer that indicates the relative order of the objects being compared. + public static int CompareToNullable(this T left, T? right) where T : struct, IComparable => + right.HasValue ? left.CompareTo(right.Value) : 1; + + /// + /// Compares the current instance with the specified instance. + /// + /// The left-hand instance to compare. + /// The right-hand instance to compare. + /// The underlying type of the current . + /// Returns a signed integer that indicates the relative order of the objects being compared. + /// If the specified object is not , or of the specified type. + public static int CompareToObject(this IComparable left, object? right) => right switch { - return value.CompareTo(min) is 1 && value.CompareTo(max) is -1; - } + null => 1, + T other => left.CompareTo(other), + _ => throw new ArgumentException($"Object must be of type {typeof(T).FullName}", nameof(right)) + }; /// /// Compares the current instance with the specified instance. /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. + /// The left-hand instance to compare. + /// The right-hand instance to compare. /// The underlying type of the current . /// Returns a signed integer that indicates the relative order of the objects being compared. /// If the specified object is not , or of the specified type. - public static int CompareToObject(this IComparable comparable, object? obj) => obj switch + public static int CompareToObject(this T left, object? right) where T : IComparable => right switch { null => 1, - T other => comparable.CompareTo(other), - _ => throw new ArgumentException($"Object must be of type {typeof(T).FullName}", nameof(obj)) + T other => left.CompareTo(other), + _ => throw new ArgumentException($"Object must be of type {typeof(T).FullName}", nameof(right)) }; /// /// Gets a record-like representation of the current instance. + /// This method is designed specifically for record-like objects and may produce undesirable results when applied to primitive-like objects. /// /// The current instance. /// Returns a record-like representation of the current instance. - public static string ToRecordString(this object value) + public static string ToRecordString(this object? value) { + if (value is null) return Null; + Type type = value.GetType(); - IEnumerable properties = type - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(property => $"{property.Name} = {property.GetValue(value)}"); + try + { + // ReSharper disable once HeapView.ObjectAllocation.Evident + StringBuilder builder = new(); + IEnumerable properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + + builder.Append(type.GetName()); + + if (properties.IsEmpty()) + return builder.Append(ObjectEmptyBrackets).ToString(); + + builder.Append(ObjectOpenBracket); - return $"{type.GetName()} {{ {string.Join(", ", properties)} }}"; + // ReSharper disable once HeapView.ObjectAllocation.Possible + foreach (PropertyInfo property in properties) + { + builder.Append(property.Name).Append(ObjectPropertyAssignment); + + object? propertyValue = property.GetValue(value); + + switch (propertyValue) + { + case null: + builder.Append(Null); + break; + case string: + builder.Append(propertyValue.ToStringOrNull()); + break; + case IEnumerable enumerable: + builder.Append(enumerable.ToCollectionString()); + break; + default: + builder.Append(propertyValue.ToStringOrNull()); + break; + } + + builder.Append(ObjectPropertySeparator); + } + + return builder.TrimEnd(ObjectPropertySeparator).Append(ObjectCloseBracket).ToString(); + } + catch + { + return string.Concat(type.GetName(), ObjectEmptyBrackets); + } } + + /// + /// Obtains a representation of the current , or a string literal null if the current object is . + /// + /// The current from which to obtain a representation. + /// Returns a representation of the current , or a string literal null if the current object is . + public static string ToStringOrNull(this object? value) => value?.ToString() ?? Null; } diff --git a/OnixLabs.Core/Extensions.ReadOnlySequence.cs b/OnixLabs.Core/Extensions.ReadOnlySequence.cs index 383b5a8..ee9190b 100644 --- a/OnixLabs.Core/Extensions.ReadOnlySequence.cs +++ b/OnixLabs.Core/Extensions.ReadOnlySequence.cs @@ -37,6 +37,7 @@ public static void CopyTo(this ReadOnlySequence sequence, out T[] array) } else { + // ReSharper disable once HeapView.ObjectAllocation.Evident array = new T[sequence.Length]; sequence.CopyTo(array); } diff --git a/OnixLabs.Core/Extensions.String.cs b/OnixLabs.Core/Extensions.String.cs index a958a3e..11b1104 100644 --- a/OnixLabs.Core/Extensions.String.cs +++ b/OnixLabs.Core/Extensions.String.cs @@ -63,8 +63,8 @@ public static class StringExtensions /// Returns a sub-string before the specified index. If the index is less than zero, then the default value will be returned. /// If the default value is , then the current instance will be returned. /// - private static string SubstringBeforeIndex(this string value, int index, string? defaultValue = null) => - index <= NotFound ? defaultValue ?? value : value[..index]; + // ReSharper disable once HeapView.ObjectAllocation + private static string SubstringBeforeIndex(this string value, int index, string? defaultValue = null) => index <= NotFound ? defaultValue ?? value : value[..index]; /// /// Obtains a sub-string after the specified index within the current instance. @@ -80,8 +80,8 @@ private static string SubstringBeforeIndex(this string value, int index, string? /// Returns a sub-string after the specified index. If the index is less than zero, then the default value will be returned. /// If the default value is , then the current instance will be returned. /// - private static string SubstringAfterIndex(this string value, int index, int offset, string? defaultValue = null) => - index <= NotFound ? defaultValue ?? value : value[(index + offset)..value.Length]; + // ReSharper disable once HeapView.ObjectAllocation + private static string SubstringAfterIndex(this string value, int index, int offset, string? defaultValue = null) => index <= NotFound ? defaultValue ?? value : value[(index + offset)..value.Length]; /// /// Obtains a sub-string before the first occurrence of the specified delimiter within the current instance. @@ -280,6 +280,7 @@ public static TimeOnly ToTimeOnly(this string value, IFormatProvider? provider = /// Returns a with all escape characters formatted as escape character literals. public static string ToEscapedString(this string value) { + // ReSharper disable once HeapView.ObjectAllocation.Evident StringBuilder result = new(); foreach (char c in value) @@ -335,7 +336,7 @@ public static bool TryCopyTo(this string value, Span destination, out int /// The that should precede the current instance. /// The that should succeed the current instance. /// Returns a new instance representing the current instance, wrapped between the specified before and after instances. - public static string Wrap(this string value, char before, char after) => $"{before}{value}{after}"; + public static string Wrap(this string value, char before, char after) => string.Concat(before.ToString(), value, after.ToString()); /// /// Wraps the current instance between the specified before and after instances. @@ -344,5 +345,5 @@ public static bool TryCopyTo(this string value, Span destination, out int /// The that should precede the current instance. /// The that should succeed the current instance. /// Returns a new instance representing the current instance, wrapped between the specified before and after instances. - public static string Wrap(this string value, string before, string after) => $"{before}{value}{after}"; + public static string Wrap(this string value, string before, string after) => string.Concat(before, value, after); } diff --git a/OnixLabs.Core/IBinaryConvertible.cs b/OnixLabs.Core/IBinaryConvertible.cs index a6bace1..e4cdd78 100644 --- a/OnixLabs.Core/IBinaryConvertible.cs +++ b/OnixLabs.Core/IBinaryConvertible.cs @@ -15,7 +15,7 @@ namespace OnixLabs.Core; /// -/// Defines a type that is convertible to a binary representation. +/// Defines a type that is convertible to an instance of . /// public interface IBinaryConvertible { diff --git a/OnixLabs.Core/ISpanBinaryConvertible.cs b/OnixLabs.Core/ISpanBinaryConvertible.cs new file mode 100644 index 0000000..99ad776 --- /dev/null +++ b/OnixLabs.Core/ISpanBinaryConvertible.cs @@ -0,0 +1,29 @@ +// 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; + +namespace OnixLabs.Core; + +/// +/// Defines a type that is convertible to an instance of . +/// +public interface ISpanBinaryConvertible : IBinaryConvertible +{ + /// + /// Gets the underlying representation of the current instance as a new instance. + /// + /// Return the underlying representation of the current instance as a new instance. + ReadOnlySpan ToReadOnlySpan(); +} diff --git a/OnixLabs.Core/IValueEquatable.cs b/OnixLabs.Core/IValueEquatable.cs index eafb819..0a9e7a6 100644 --- a/OnixLabs.Core/IValueEquatable.cs +++ b/OnixLabs.Core/IValueEquatable.cs @@ -28,7 +28,7 @@ public interface IValueEquatable : IEquatable where T : IValueEquatable /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static abstract bool operator ==(T left, T right); + public static abstract bool operator ==(T? left, T? right); /// /// Performs an inequality comparison between two object instances. @@ -36,5 +36,5 @@ public interface IValueEquatable : IEquatable where T : IValueEquatable /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static abstract bool operator !=(T left, T right); + public static abstract bool operator !=(T? left, T? right); } diff --git a/OnixLabs.Core/Linq/Extensions.IEnumerable.cs b/OnixLabs.Core/Linq/Extensions.IEnumerable.cs index 52845da..011f16d 100644 --- a/OnixLabs.Core/Linq/Extensions.IEnumerable.cs +++ b/OnixLabs.Core/Linq/Extensions.IEnumerable.cs @@ -13,10 +13,14 @@ // limitations under the License. using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Numerics; +using System.Text; +using OnixLabs.Core.Collections.Generic; +using OnixLabs.Core.Text; namespace OnixLabs.Core.Linq; @@ -27,27 +31,96 @@ namespace OnixLabs.Core.Linq; [EditorBrowsable(EditorBrowsableState.Never)] public static class IEnumerableExtensions { + private const string CollectionSeparator = ", "; + private const char CollectionOpenBracket = '['; + private const char CollectionCloseBracket = ']'; + + private const string EnumerableNullExceptionMessage = "Enumerable must not be null."; + private const string SelectorNullExceptionMessage = "Selector must not be null."; + private const string PredicateNullExceptionMessage = "Predicate must not be null."; + private const string ActionNullExceptionMessage = "Action must not be null."; + /// /// Determines whether all elements of the current are equal by a specified property. /// /// The current on which to perform the operation. /// The selector function which will be used to select the desired property from each element. + /// The equality comparer that will be used to compare objects of type , or to use the default equality comparer. /// The underlying type of the . /// The underlying type of each selected element. /// Returns if all selected element properties are equal; otherwise . - public static bool AllEqualBy(this IEnumerable enumerable, Func selector) => - enumerable.Select(selector).Distinct().IsSingle(); + public static bool AllEqualBy(this IEnumerable enumerable, Func selector, IEqualityComparer? comparer = null) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + RequireNotNull(selector, SelectorNullExceptionMessage, nameof(selector)); + + using IEnumerator enumerator = enumerable.GetEnumerator(); + + // AllEqualBy of empty IEnumerable is considered true. + if (!enumerator.MoveNext()) return true; + + TProperty firstValue = selector(enumerator.Current); + + while (enumerator.MoveNext()) + if (!comparer.GetOrDefault().Equals(firstValue, selector(enumerator.Current))) + return false; + + return true; + } /// /// Determines whether any elements of the current are equal by a specified property. /// /// The current on which to perform the operation. /// The selector function which will be used to select the desired property from each element. + /// The equality comparer that will be used to compare objects of type , or to use the default equality comparer. /// The underlying type of the . /// The underlying type of each selected element. /// Returns if any selected element properties are equal; otherwise . - public static bool AnyEqualBy(this IEnumerable enumerable, Func selector) => - enumerable.GroupBy(selector).Any(group => group.Count() > 1); + public static bool AnyEqualBy(this IEnumerable enumerable, Func selector, IEqualityComparer? comparer = null) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + RequireNotNull(selector, SelectorNullExceptionMessage, nameof(selector)); + + // ReSharper disable once HeapView.ObjectAllocation.Evident + HashSet seenValues = new(comparer); + + // ReSharper disable once LoopCanBeConvertedToQuery, HeapView.ObjectAllocation.Possible + foreach (T item in enumerable) + { + TProperty value = selector(item); + + if (seenValues.Add(value)) continue; + + return true; + } + + return false; + } + + /// + /// Obtains a number that represents how many elements are in the current . + /// + /// The current on which to perform the operation. + /// Returns a number that represents how many elements are in the current . + public static int Count(this IEnumerable enumerable) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + int count = 0; + + // ReSharper disable once HeapView.ObjectAllocation.Possible + foreach (object? _ in enumerable) + checked + { + ++count; + } + + return count; + } /// /// Obtains a number that represents how many elements in the current do not satisfy the specified predicate condition. @@ -56,8 +129,24 @@ public static bool AnyEqualBy(this IEnumerable enumerable, Func /// The function to test each element for a condition. /// The underlying type of the . /// Returns a number that represents how many elements in the current do not satisfy the specified predicate condition. - public static int CountNot(this IEnumerable enumerable, Func predicate) => - enumerable.Count(element => !predicate(element)); + public static int CountNot(this IEnumerable enumerable, Func predicate) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + RequireNotNull(predicate, PredicateNullExceptionMessage, nameof(predicate)); + + int result = 0; + + // ReSharper disable once HeapView.ObjectAllocation.Possible + foreach (T element in enumerable) + if (!predicate(element)) + checked + { + ++result; + } + + return result; + } /// /// Obtains the first element of the current that satisfies the specified predicate; @@ -70,8 +159,28 @@ public static int CountNot(this IEnumerable enumerable, Func pred /// Returns the first element of the current that satisfies the specified predicate; /// otherwise if no such element is found. /// - public static Optional FirstOrNone(this IEnumerable enumerable, Func? predicate = null) where T : notnull => - Optional.Of(enumerable.FirstOrDefault(predicate ?? (_ => true))); + public static Optional FirstOrNone(this IEnumerable enumerable, Func? predicate = null) where T : notnull + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + return Optional.Of(enumerable.FirstOrDefault(predicate ?? (_ => true))); + } + + /// + /// Performs the specified for each element of the current . + /// + /// The current over which to iterate. + /// The to perform for each element. + public static void ForEach(this IEnumerable enumerable, Action action) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + RequireNotNull(action, ActionNullExceptionMessage, nameof(action)); + + // ReSharper disable once HeapView.ObjectAllocation.Possible + foreach (object? element in enumerable) action(element); + } /// /// Performs the specified for each element of the current . @@ -81,9 +190,31 @@ public static Optional FirstOrNone(this IEnumerable enumerable, FuncThe underlying type of the . public static void ForEach(this IEnumerable enumerable, Action action) { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + RequireNotNull(action, ActionNullExceptionMessage, nameof(action)); + + // ReSharper disable once HeapView.ObjectAllocation.Possible foreach (T element in enumerable) action(element); } + /// + /// Gets the content hash code of the elements of the current . + /// + /// The current from which to compute a content hash code. + /// Returns the computed content hash code of the current . + public static int GetContentHashCode(this IEnumerable? enumerable) + { + if (enumerable is null) return default; + + HashCode result = new(); + + // ReSharper disable once HeapView.ObjectAllocation.Possible + foreach (object? element in enumerable) result.Add(element); + + return result.ToHashCode(); + } + /// /// Gets the content hash code of the elements of the current . /// @@ -95,17 +226,52 @@ public static int GetContentHashCode(this IEnumerable? enumerable) if (enumerable is null) return default; HashCode result = new(); - enumerable.ForEach(element => result.Add(element)); + + // ReSharper disable once HeapView.ObjectAllocation.Possible + foreach (T element in enumerable) result.Add(element); + return result.ToHashCode(); } + /// + /// Determines whether the current is empty. + /// + /// The current on which to perform the operation. + /// Returns if the is empty; otherwise, . + public static bool IsEmpty(this IEnumerable enumerable) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + // ReSharper disable once LoopCanBeConvertedToQuery, HeapView.ObjectAllocation.Possible + foreach (object? _ in enumerable) return false; + + return true; + } + /// /// Determines whether the current is empty. /// /// The current on which to perform the operation. /// The underlying type of the . /// Returns if the is empty; otherwise, . - public static bool IsEmpty(this IEnumerable enumerable) => !enumerable.Any(); + public static bool IsEmpty(this IEnumerable enumerable) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + // ReSharper disable once LoopCanBeConvertedToQuery, HeapView.ObjectAllocation.Possible + foreach (T _ in enumerable) return false; + + return true; + } + + /// + /// Determines whether the current is not empty. + /// + /// The current on which to perform the operation. + /// Returns if the is not empty; otherwise, . + public static bool IsNotEmpty(this IEnumerable enumerable) => !enumerable.IsEmpty(); /// /// Determines whether the current is not empty. @@ -113,7 +279,33 @@ public static int GetContentHashCode(this IEnumerable? enumerable) /// The current on which to perform the operation. /// The underlying type of the . /// Returns if the is not empty; otherwise, . - public static bool IsNotEmpty(this IEnumerable enumerable) => enumerable.Any(); + public static bool IsNotEmpty(this IEnumerable enumerable) => !enumerable.IsEmpty(); + + /// + /// Determines whether the current contains a single element. + /// + /// The current on which to perform the operation. + /// Returns if the contains a single element; otherwise, . + public static bool IsSingle(this IEnumerable enumerable) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + long count = 0; + + // ReSharper disable once HeapView.ObjectAllocation.Possible + foreach (object? _ in enumerable) + { + checked + { + ++count; + } + + if (count > 1) return false; + } + + return count is 1; + } /// /// Determines whether the current contains a single element. @@ -121,7 +313,39 @@ public static int GetContentHashCode(this IEnumerable? enumerable) /// The current on which to perform the operation. /// The underlying type of the . /// Returns if the contains a single element; otherwise, . - public static bool IsSingle(this IEnumerable enumerable) => enumerable.LongCount() == 1; + public static bool IsSingle(this IEnumerable enumerable) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + long count = 0; + + // ReSharper disable once HeapView.ObjectAllocation.Possible + foreach (T _ in enumerable) + { + checked + { + ++count; + } + + if (count > 1) return false; + } + + return count is 1; + } + + /// + /// Determines whether the current contains an even number of elements. + /// + /// The current on which to perform the operation. + /// Returns if the contains an even number of elements; otherwise, . + public static bool IsCountEven(this IEnumerable enumerable) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + return enumerable.LongCount() % 2 is 0; + } /// /// Determines whether the current contains an even number of elements. @@ -130,8 +354,20 @@ public static int GetContentHashCode(this IEnumerable? enumerable) /// The function to test each element for a condition. /// The underlying type of the . /// Returns if the contains an even number of elements; otherwise, . - public static bool IsCountEven(this IEnumerable enumerable, Func? predicate = null) => - enumerable.LongCount(element => predicate?.Invoke(element) ?? true) % 2 == 0; + public static bool IsCountEven(this IEnumerable enumerable, Func? predicate = null) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + return enumerable.LongCount(predicate ?? (_ => true)) % 2 is 0; + } + + /// + /// Determines whether the current contains an odd number of elements. + /// + /// The current on which to perform the operation. + /// Returns if the contains an odd number of elements; otherwise, . + public static bool IsCountOdd(this IEnumerable enumerable) => !enumerable.IsCountEven(); /// /// Determines whether the current contains an odd number of elements. @@ -140,17 +376,52 @@ public static bool IsCountEven(this IEnumerable enumerable, Func? /// The function to test each element for a condition. /// The underlying type of the . /// Returns if the contains an odd number of elements; otherwise, . - public static bool IsCountOdd(this IEnumerable enumerable, Func? predicate = null) => - enumerable.LongCount(element => predicate?.Invoke(element) ?? true) % 2 != 0; + public static bool IsCountOdd(this IEnumerable enumerable, Func? predicate = null) => !enumerable.IsCountEven(predicate); + + /// + /// Joins the elements of the current into a new instance. + /// + /// The current to join. + /// The separator which will appear between joined elements. + /// Returns the elements of the current , joined into a new instance. + public static string JoinToString(this IEnumerable enumerable, string separator = CollectionSeparator) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + // ReSharper disable once HeapView.ObjectAllocation.Evident + StringBuilder builder = new(); + + // ReSharper disable once HeapView.ObjectAllocation.Possible + foreach (object? element in enumerable) builder.Append(element.ToStringOrNull()).Append(separator); + + builder.TrimEnd(separator); + + return builder.ToString(); + } /// - /// Joins the elements of the current into a instance. + /// Joins the elements of the current into a new instance. /// /// The current to join. /// The separator which will appear between joined elements. /// The underlying type of the . /// Returns the elements of the current , joined into a new instance. - public static string JoinToString(this IEnumerable enumerable, string separator = ", ") => string.Join(separator, enumerable); + public static string JoinToString(this IEnumerable enumerable, string separator = CollectionSeparator) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + // ReSharper disable once HeapView.ObjectAllocation.Evident + StringBuilder builder = new(); + + // ReSharper disable once HeapView.ObjectAllocation.Possible, HeapView.PossibleBoxingAllocation + foreach (T element in enumerable) builder.Append(element.ToStringOrNull()).Append(separator); + + builder.TrimEnd(separator); + + return builder.ToString(); + } /// /// Obtains the last element of the current that satisfies the specified predicate; @@ -163,8 +434,36 @@ public static bool IsCountOdd(this IEnumerable enumerable, Func? /// Returns the last element of the current that satisfies the specified predicate; /// otherwise if no such element is found. /// - public static Optional LastOrNone(this IEnumerable enumerable, Func? predicate = null) where T : notnull => - Optional.Of(enumerable.LastOrDefault(predicate ?? (_ => true))); + public static Optional LastOrNone(this IEnumerable enumerable, Func? predicate = null) where T : notnull + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + return Optional.Of(enumerable.LastOrDefault(predicate ?? (_ => true))); + } + + /// + /// Obtains a number that represents how many elements are in the current . + /// + /// The current on which to perform the operation. + /// Returns a number that represents how many elements are in the current . + // ReSharper disable once MemberCanBePrivate.Global + public static long LongCount(this IEnumerable enumerable) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + long count = 0; + + // ReSharper disable once HeapView.ObjectAllocation.Possible + foreach (object? _ in enumerable) + checked + { + ++count; + } + + return count; + } /// /// Determines whether none of the elements of the current satisfy the specified predicate condition. @@ -173,7 +472,13 @@ public static Optional LastOrNone(this IEnumerable enumerable, FuncThe function to test each element for a condition. /// The underlying type of the . /// Returns if none of the elements of the current satisfy the specified predicate condition; otherwise, . - public static bool None(this IEnumerable enumerable, Func predicate) => !enumerable.Any(predicate); + public static bool None(this IEnumerable enumerable, Func predicate) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + return !enumerable.Any(predicate); + } /// Determines whether two sequences are , or equal by comparing their elements by using a specified . /// An to compare to . @@ -181,10 +486,10 @@ public static Optional LastOrNone(this IEnumerable enumerable, FuncAn to use to compare elements. /// The underlying type of the . /// Returns if the two source sequences are , or of equal length and their corresponding elements compare equal according to ; otherwise, . - public static bool SequenceEqualOrNull(this IEnumerable? enumerable, IEnumerable? other, EqualityComparer? comparer = null) + public static bool SequenceEqualOrNull(this IEnumerable? enumerable, IEnumerable? other, IEqualityComparer? comparer = null) { if (enumerable is null || other is null) return enumerable is null && other is null; - return enumerable.SequenceEqual(other, comparer ?? EqualityComparer.Default); + return enumerable.SequenceEqual(other, comparer.GetOrDefault()); } /// @@ -200,8 +505,20 @@ public static bool SequenceEqualOrNull(this IEnumerable? enumerable, IEnum /// otherwise if no such element is found, /// or if the current contains more than one matching element. /// - public static Result> SingleOrNone(this IEnumerable enumerable, Func? predicate = null) where T : notnull => - Result>.Of(() => Optional.Of(enumerable.SingleOrDefault(predicate ?? (_ => true)))); + public static Result> SingleOrNone(this IEnumerable enumerable, Func? predicate = null) where T : notnull + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + try + { + return Optional.Of(enumerable.SingleOrDefault(predicate ?? (_ => true))); + } + catch (Exception exception) + { + return exception; + } + } /// /// Calculates the sum of the elements of the current . @@ -211,8 +528,10 @@ public static Result> SingleOrNone(this IEnumerable enumerable /// Returns the sum of the elements of the current . public static T Sum(this IEnumerable enumerable) where T : INumberBase { - IEnumerable numbers = enumerable as T[] ?? enumerable.ToArray(); - return numbers.IsEmpty() ? T.Zero : numbers.Aggregate((left, right) => left + right); + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + return enumerable.IsEmpty() ? T.Zero : enumerable.Aggregate((left, right) => left + right); } /// @@ -223,8 +542,14 @@ public static T Sum(this IEnumerable enumerable) where T : INumberBase /// The underlying type of the . /// The underlying type of each element to sum. /// Returns the sum of the elements of the current . - public static TResult SumBy(this IEnumerable enumerable, Func selector) where TResult : INumberBase => - enumerable.Select(selector).Sum(); + public static TResult SumBy(this IEnumerable enumerable, Func selector) where TResult : INumberBase + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + RequireNotNull(selector, SelectorNullExceptionMessage, nameof(selector)); + + return enumerable.Select(selector).Sum(); + } /// /// Filters the current elements that do not satisfy the specified predicate condition. @@ -233,8 +558,17 @@ public static TResult SumBy(this IEnumerable enumerable, FuncThe function to test each element for a condition. /// The underlying type of the . /// Returns a new that contains elements that do not satisfy the condition. - public static IEnumerable WhereNot(this IEnumerable enumerable, Func predicate) => - enumerable.Where(element => !predicate(element)); + public static IEnumerable WhereNot(this IEnumerable enumerable, Func predicate) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + RequireNotNull(predicate, PredicateNullExceptionMessage, nameof(predicate)); + + // ReSharper disable once LoopCanBeConvertedToQuery, HeapView.ObjectAllocation.Possible + foreach (T element in enumerable) + if (!predicate(element)) + yield return element; + } /// /// Filters the current elements that are not . @@ -242,7 +576,40 @@ public static IEnumerable WhereNot(this IEnumerable enumerable, FuncThe current on which to perform the operation. /// The underlying type of the . /// Returns a new that contains elements that are not . - public static IEnumerable WhereNotNull(this IEnumerable enumerable) where T : notnull => enumerable.Where(element => element is not null)!; + public static IEnumerable WhereNotNull(this IEnumerable enumerable) where T : class + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + return enumerable.Where(element => element is not null)!; + } + + /// + /// Filters the current elements that are not . + /// + /// The current on which to perform the operation. + /// The underlying type of the . + /// Returns a new that contains elements that are not . + public static IEnumerable WhereNotNull(this IEnumerable enumerable) where T : struct + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + return enumerable.Where(element => element.HasValue).Select(element => element!.Value); + } + + /// + /// Formats the current as a collection string. + /// + /// The current to format. + /// Returns a new instance representing the current . + public static string ToCollectionString(this IEnumerable enumerable) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + return enumerable.JoinToString().Wrap(CollectionOpenBracket, CollectionCloseBracket); + } /// /// Formats the current as a collection string. @@ -250,7 +617,13 @@ public static IEnumerable WhereNot(this IEnumerable enumerable, FuncThe current to format. /// The underlying type of the . /// Returns a new instance representing the current . - public static string ToCollectionString(this IEnumerable enumerable) => enumerable.JoinToString().Wrap('[', ']'); + public static string ToCollectionString(this IEnumerable enumerable) + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + return enumerable.JoinToString().Wrap(CollectionOpenBracket, CollectionCloseBracket); + } /// /// Formats the current as a collection string. @@ -260,6 +633,13 @@ public static IEnumerable WhereNot(this IEnumerable enumerable, FuncAn object that provides culture-specific formatting information. /// The underlying type of the . /// Returns a new instance representing the current . - public static string ToCollectionString(this IEnumerable enumerable, string format, IFormatProvider? provider = null) where T : IFormattable => - enumerable.Select(element => element.ToString(format, provider)).ToCollectionString(); + // ReSharper disable once HeapView.ClosureAllocation + public static string ToCollectionString(this IEnumerable enumerable, string format, IFormatProvider? provider = null) where T : IFormattable + { + // ReSharper disable PossibleMultipleEnumeration + RequireNotNull(enumerable, EnumerableNullExceptionMessage, nameof(enumerable)); + + // ReSharper disable once HeapView.DelegateAllocation + return enumerable.Select(element => element.ToString(format, provider)).ToCollectionString(); + } } diff --git a/OnixLabs.Core/OnixLabs.Core.csproj b/OnixLabs.Core/OnixLabs.Core.csproj index b15f158..eb57bb0 100644 --- a/OnixLabs.Core/OnixLabs.Core.csproj +++ b/OnixLabs.Core/OnixLabs.Core.csproj @@ -7,11 +7,11 @@ OnixLabs.Core ONIXLabs ONIXLabs Core API for .NET - 8.16.0 + 9.0.0 en Copyright © ONIXLabs 2020 https://github.com/onix-labs/onixlabs-dotnet - 8.16.0 + 9.0.0 $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb diff --git a/OnixLabs.Core/Optional.None.cs b/OnixLabs.Core/Optional.None.cs deleted file mode 100644 index 6da2ea6..0000000 --- a/OnixLabs.Core/Optional.None.cs +++ /dev/null @@ -1,145 +0,0 @@ -// 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; - -namespace OnixLabs.Core; - -/// -/// Represents an absent optional value. -/// -/// The type of the underlying optional value. -public sealed class None : Optional, IValueEquatable> where T : notnull -{ - /// - /// Initializes a new instance of the class. - /// - internal None() - { - } - - /// - /// Performs an equality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(None left, None right) => Equals(left, right); - - /// - /// Performs an inequality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(None left, None right) => !Equals(left, right); - - /// - /// Checks whether the current object is equal to another object of the same type. - /// - /// An object to compare with the current object. - /// Returns if the current object is equal to the other parameter; otherwise, . - public bool Equals(None? other) => ReferenceEquals(this, other) || other is not null; - - /// - /// Checks for equality between the current instance and another object. - /// - /// The object to check for equality. - /// Returns if the object is equal to the current instance; otherwise, . - public override bool Equals(object? obj) => Equals(obj as None); - - /// - /// Serves as a hash code function for the current instance. - /// - /// Returns a hash code for the current instance. - public override int GetHashCode() => default; - - /// - /// Gets the underlying value of the current instance, if the underlying value is present; - /// otherwise, returns the default value. - /// - /// - /// Returns the underlying value of the current instance, if the underlying value is present; - /// otherwise, returns the default value. - /// - public override T? GetValueOrDefault() => default; - - /// - /// Gets the underlying value of the current instance, if the underlying value is present; - /// otherwise, returns the specified default value. - /// - /// The default value to return in the event that the underlying value is absent. - /// - /// Returns the underlying value of the current instance, if the underlying value is present; - /// otherwise, returns the specified default value. - /// - public override T GetValueOrDefault(T defaultValue) => defaultValue; - - /// - /// Gets the underlying value of the current instance, if the underlying value is present; - /// otherwise, throws an exception. - /// - /// - /// Returns the underlying value of the current instance, if the underlying value is present; - /// otherwise, throws an exception. - /// - public override T GetValueOrThrow() => throw new InvalidOperationException($"Optional value of type {typeof(T).FullName} is not present."); - - /// - /// Executes the action that matches the value of the current instance. - /// - /// The action to execute when the underlying value of the current instance is present. - /// The action to execute when the underlying value of the current instance is absent. - public override void Match(Action? some = null, Action? none = null) => none?.Invoke(); - - /// - /// Executes the function that matches the value of the current instance and returns its result. - /// - /// The function to execute when the underlying value of the current instance is present. - /// The function to execute when the underlying value of the current instance is absent. - /// The underlying type of the result produced by the matching function. - /// - /// Returns the result of the function if the underlying value of the current value is present; - /// otherwise, returns the result of the function if the underlying value of the current value is absent. - /// - public override TResult Match(Func some, Func none) => none(); - - /// - /// Applies the provided selector function to the value of the current instance. - /// - /// The function to apply to the value of the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns a new instance containing the result of the function if the current - /// instance has an underlying value; otherwise, . - /// - public override Optional Select(Func selector) => Optional.None; - - /// - /// Applies the provided selector function to the value of the current instance. - /// - /// The function to apply to the value of the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns a new instance containing the result of the function if the current - /// instance has an underlying value; otherwise, . - /// - public override Optional SelectMany(Func> selector) => Optional.None; - - /// - /// Returns a that represents the current object. - /// - /// Returns a that represents the current object. - public override string ToString() => nameof(None); -} diff --git a/OnixLabs.Core/Optional.Some.cs b/OnixLabs.Core/Optional.Some.cs deleted file mode 100644 index 3d11b48..0000000 --- a/OnixLabs.Core/Optional.Some.cs +++ /dev/null @@ -1,150 +0,0 @@ -// 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; - -namespace OnixLabs.Core; - -/// -/// Represents a present optional value. -/// -/// The type of the underlying optional value. -public sealed class Some : Optional, IValueEquatable> where T : notnull -{ - /// - /// Initializes a new instance of the class. - /// - /// The underlying optional value. - internal Some(T value) => Value = value; - - /// - /// Gets the underlying optional value. - /// - // ReSharper disable once MemberCanBePrivate.Global - public T Value { get; } - - /// - /// Performs an equality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Some left, Some right) => Equals(left, right); - - /// - /// Performs an inequality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Some left, Some right) => !Equals(left, right); - - /// - /// Checks whether the current object is equal to another object of the same type. - /// - /// An object to compare with the current object. - /// Returns if the current object is equal to the other parameter; otherwise, . - public bool Equals(Some? other) => ReferenceEquals(this, other) || other is not null && Equals(other.Value, Value); - - /// - /// Checks for equality between the current instance and another object. - /// - /// The object to check for equality. - /// Returns if the object is equal to the current instance; otherwise, . - public override bool Equals(object? obj) => Equals(obj as Some); - - /// - /// Serves as a hash code function for the current instance. - /// - /// Returns a hash code for the current instance. - public override int GetHashCode() => Value.GetHashCode(); - - /// - /// Gets the underlying value of the current instance, if the underlying value is present; - /// otherwise, returns the default value. - /// - /// - /// Returns the underlying value of the current instance, if the underlying value is present; - /// otherwise, returns the default value. - /// - public override T GetValueOrDefault() => Value; - - /// - /// Gets the underlying value of the current instance, if the underlying value is present; - /// otherwise, returns the specified default value. - /// - /// The default value to return in the event that the underlying value is absent. - /// - /// Returns the underlying value of the current instance, if the underlying value is present; - /// otherwise, returns the specified default value. - /// - public override T GetValueOrDefault(T defaultValue) => Value; - - /// - /// Gets the underlying value of the current instance, if the underlying value is present; - /// otherwise, throws an exception. - /// - /// - /// Returns the underlying value of the current instance, if the underlying value is present; - /// otherwise, throws an exception. - /// - public override T GetValueOrThrow() => Value; - - /// - /// Executes the action that matches the value of the current instance. - /// - /// The action to execute when the underlying value of the current instance is present. - /// The action to execute when the underlying value of the current instance is absent. - public override void Match(Action? some = null, Action? none = null) => some?.Invoke(Value); - - /// - /// Executes the function that matches the value of the current instance and returns its result. - /// - /// The function to execute when the underlying value of the current instance is present. - /// The function to execute when the underlying value of the current instance is absent. - /// The underlying type of the result produced by the matching function. - /// - /// Returns the result of the function if the underlying value of the current value is present; - /// otherwise, returns the result of the function if the underlying value of the current value is absent. - /// - public override TResult Match(Func some, Func none) => some(Value); - - /// - /// Applies the provided selector function to the value of the current instance. - /// - /// The function to apply to the value of the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns a new instance containing the result of the function if the current - /// instance has an underlying value; otherwise, . - /// - public override Optional Select(Func selector) => selector(Value); - - /// - /// Applies the provided selector function to the value of the current instance. - /// - /// The function to apply to the value of the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns a new instance containing the result of the function if the current - /// instance has an underlying value; otherwise, . - /// - public override Optional SelectMany(Func> selector) => selector(Value); - - /// - /// Returns a that represents the current object. - /// - /// Returns a that represents the current object. - public override string ToString() => Value.ToString() ?? string.Empty; -} diff --git a/OnixLabs.Core/Optional.cs b/OnixLabs.Core/Optional.cs index a7ae86b..996165b 100644 --- a/OnixLabs.Core/Optional.cs +++ b/OnixLabs.Core/Optional.cs @@ -26,7 +26,8 @@ public abstract class Optional : IValueEquatable> where T : notnu /// /// Gets a value indicating that the optional value is absent. /// - public static readonly None None = new(); + // ReSharper disable once HeapView.ObjectAllocation.Evident + public static readonly None None = None.Instance; /// /// Initializes a new instance of the class. @@ -38,7 +39,7 @@ internal Optional() /// /// Gets a value indicating whether the underlying value of the current instance is present. /// - public bool HasValue => this is Some; + public bool HasValue => IsSome(this); /// /// Gets a value indicating whether the specified instance is . @@ -67,9 +68,8 @@ internal Optional() /// Returns a new instance of the class, where the underlying value is present if /// the specified value is not ; otherwise, the underlying value is . /// - public static Optional Of(T? value) => value is not null && !EqualityComparer.Default.Equals(value, default) - ? Some(value) - : None; + // ReSharper disable once HeapView.PossibleBoxingAllocation + public static Optional Of(T? value) => value is null || EqualityComparer.Default.Equals(value, default) ? None : Some(value); /// /// Creates a new instance of the class, where the underlying value is present if @@ -93,18 +93,20 @@ public static Optional Of(TStruct? value) where TStruct : stru /// Returns a new instance of the class, where the underlying value is present if /// the specified value is not ; otherwise, the underlying value is . /// + // ReSharper disable once HeapView.ObjectAllocation.Evident public static Some Some(T value) => new(RequireNotNull(value, "Value must not be null.", nameof(value))); /// /// Creates a new instance of the class, where the underlying value is present if - /// the specified value is not ; otherwise, the underlying value is . + /// the specified value is not ; otherwise, the underlying value is . /// /// The underlying optional value. /// /// Returns a new instance of the class, where the underlying value is present if - /// the specified value is not ; otherwise, the underlying value is . + /// the specified value is not ; otherwise, the underlying value is . /// - public static implicit operator Optional(T? value) => value is not null ? Some(value) : None; + // ReSharper disable once HeapView.PossibleBoxingAllocation + public static implicit operator Optional(T? value) => value is null ? None : Some(value); /// /// Gets the underlying value of the specified instance; @@ -124,7 +126,7 @@ public static Optional Of(TStruct? value) where TStruct : stru /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Optional left, Optional right) => Equals(left, right); + public static bool operator ==(Optional? left, Optional? right) => Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -132,32 +134,28 @@ public static Optional Of(TStruct? value) where TStruct : stru /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Optional left, Optional right) => !Equals(left, right); + public static bool operator !=(Optional? left, Optional? right) => !Equals(left, right); /// /// Checks whether the current object is equal to another object of the same type. /// /// An object to compare with the current object. /// Returns if the current object is equal to the other parameter; otherwise, . - public bool Equals(Optional? other) => this switch - { - Some some => other is Some someOther && some.Equals(someOther), - None none => other is None noneOther && none.Equals(noneOther), - _ => ReferenceEquals(this, other) - }; + public bool Equals(Optional? other) => OptionalEqualityComparer.Default.Equals(this, other); /// /// Checks for equality between the current instance and another object. /// /// The object to check for equality. /// Returns if the object is equal to the current instance; otherwise, . - public override bool Equals(object? obj) => Equals(obj as Optional); + public sealed override bool Equals(object? obj) => Equals(obj as Optional); /// /// Serves as a hash code function for the current instance. /// /// Returns a hash code for the current instance. - public override int GetHashCode() => default; + // ReSharper disable once HeapView.PossibleBoxingAllocation + public sealed override int GetHashCode() => this is Some some ? some.Value.GetHashCode() : default; /// /// Gets the underlying value of the current instance, if the underlying value is present; @@ -167,7 +165,7 @@ public static Optional Of(TStruct? value) where TStruct : stru /// Returns the underlying value of the current instance, if the underlying value is present; /// otherwise, returns the default value. /// - public abstract T? GetValueOrDefault(); + public T? GetValueOrDefault() => this is Some some ? some.Value : default; /// /// Gets the underlying value of the current instance, if the underlying value is present; @@ -178,7 +176,7 @@ public static Optional Of(TStruct? value) where TStruct : stru /// Returns the underlying value of the current instance, if the underlying value is present; /// otherwise, returns the specified default value. /// - public abstract T GetValueOrDefault(T defaultValue); + public T GetValueOrDefault(T defaultValue) => this is Some some ? some.Value : defaultValue; /// /// Gets the underlying value of the current instance, if the underlying value is present; @@ -188,14 +186,25 @@ public static Optional Of(TStruct? value) where TStruct : stru /// Returns the underlying value of the current instance, if the underlying value is present; /// otherwise, throws an exception. /// - public abstract T GetValueOrThrow(); + public T GetValueOrThrow() => this is Some some ? some.Value : throw new InvalidOperationException($"Optional value of type {typeof(T).FullName} is not present."); /// /// Executes the action that matches the value of the current instance. /// /// The action to execute when the underlying value of the current instance is present. /// The action to execute when the underlying value of the current instance is absent. - public abstract void Match(Action? some = null, Action? none = null); + public void Match(Action? some = null, Action? none = null) + { + switch (this) + { + case Some someOptional: + some?.Invoke(someOptional.Value); + break; + default: + none?.Invoke(); + break; + } + } /// /// Executes the function that matches the value of the current instance and returns its result. @@ -207,7 +216,11 @@ public static Optional Of(TStruct? value) where TStruct : stru /// Returns the result of the function if the underlying value of the current value is present; /// otherwise, returns the result of the function if the underlying value of the current value is absent. /// - public abstract TResult Match(Func some, Func none); + public TResult Match(Func some, Func none) => this switch + { + Some someOptional => some.Invoke(someOptional.Value), + _ => none.Invoke() + }; /// /// Applies the provided selector function to the value of the current instance. @@ -218,7 +231,11 @@ public static Optional Of(TStruct? value) where TStruct : stru /// Returns a new instance containing the result of the function if the current /// instance has an underlying value; otherwise, . /// - public abstract Optional Select(Func selector) where TResult : notnull; + public Optional Select(Func selector) where TResult : notnull => this switch + { + Some someOptional => selector(someOptional.Value), + _ => Optional.None + }; /// /// Applies the provided selector function to the value of the current instance. @@ -229,7 +246,11 @@ public static Optional Of(TStruct? value) where TStruct : stru /// Returns a new instance containing the result of the function if the current /// instance has an underlying value; otherwise, . /// - public abstract Optional SelectMany(Func> selector) where TResult : notnull; + public Optional SelectMany(Func> selector) where TResult : notnull => this switch + { + Some someOptional => selector(someOptional.Value), + _ => Optional.None + }; /// /// Wraps the current instance into new, successful instance. @@ -241,10 +262,41 @@ public static Optional Of(TStruct? value) where TStruct : stru /// Returns a that represents the current object. /// /// Returns a that represents the current object. - public override string ToString() => this switch + // ReSharper disable once HeapView.PossibleBoxingAllocation + public sealed override string ToString() => this is Some some ? some.Value.ToString() ?? string.Empty : nameof(None); +} + +/// +/// Represents a present optional value. +/// +/// The type of the underlying optional value. +public sealed class Some : Optional where T : notnull +{ + /// + /// Initializes a new instance of the class. + /// + /// The underlying optional value. + internal Some(T value) => Value = value; + + /// + /// Gets the underlying optional value. + /// + public T Value { get; } +} + +/// +/// Represents an absent optional value. +/// +/// The type of the underlying optional value. +public sealed class None : Optional where T : notnull +{ + // ReSharper disable once HeapView.ObjectAllocation.Evident + internal static readonly None Instance = new(); + + /// + /// Initializes a new instance of the class. + /// + private None() { - Some some => some.ToString(), - None none => none.ToString(), - _ => base.ToString() ?? GetType().FullName ?? nameof(Optional) - }; + } } diff --git a/OnixLabs.Core/OptionalEqualityComparer.cs b/OnixLabs.Core/OptionalEqualityComparer.cs index 39695dd..920ba60 100644 --- a/OnixLabs.Core/OptionalEqualityComparer.cs +++ b/OnixLabs.Core/OptionalEqualityComparer.cs @@ -19,29 +19,30 @@ namespace OnixLabs.Core; /// /// Represents an equality comparer for comparing instances. /// -/// The that will be used to compare the underlying values of each instance. +/// The that will be used to compare the underlying values of each instance. /// The underlying type of the instance. -public sealed class OptionalEqualityComparer(EqualityComparer? valueComparer = null) : EqualityComparer> where T : notnull +public sealed class OptionalEqualityComparer(EqualityComparer? valueEqualityComparer = null) : IEqualityComparer> where T : notnull { + /// + /// Gets the default instance. + /// + // ReSharper disable once HeapView.ObjectAllocation.Evident + public static readonly OptionalEqualityComparer Default = new(); + /// Determines whether the specified values are equal. /// The first object of type to compare. /// The second object of type to compare. /// Returns if the specified values are equal; otherwise, . - public override bool Equals(Optional? x, Optional? y) + public bool Equals(Optional? x, Optional? y) { + if (ReferenceEquals(x, y)) return true; if (x is null || y is null) return x is null && y is null; if (Optional.IsNone(x) && Optional.IsNone(y)) return true; - - T? xValue = x.GetValueOrDefault(); - T? yValue = y.GetValueOrDefault(); - - return (valueComparer ?? EqualityComparer.Default).Equals(xValue, yValue); + return (valueEqualityComparer ?? EqualityComparer.Default).Equals(x.GetValueOrDefault(), y.GetValueOrDefault()); } /// Returns a hash code for the specified object. /// The for which a hash code is to be returned. /// A hash code for the specified object. - public override int GetHashCode(Optional obj) => Optional.IsSome(obj) - ? (valueComparer ?? EqualityComparer.Default).GetHashCode() - : Optional.None.GetHashCode(); + public int GetHashCode(Optional obj) => obj.GetHashCode(); } diff --git a/OnixLabs.Core/Preconditions.cs b/OnixLabs.Core/Preconditions.cs index 9894f90..5ca2e3f 100644 --- a/OnixLabs.Core/Preconditions.cs +++ b/OnixLabs.Core/Preconditions.cs @@ -13,7 +13,6 @@ // limitations under the License. using System; -using System.ComponentModel; using OnixLabs.Core.Linq; namespace OnixLabs.Core; @@ -48,10 +47,8 @@ public static void Check(bool condition, string message = ArgumentFailed) /// The underlying type of the value. /// Returns a non-null value of the specified type. /// If the specified value is . - public static T CheckNotNull(T? value, string message = ArgumentNull) - { - return value ?? throw new InvalidOperationException(message); - } + public static T CheckNotNull(T? value, string message = ArgumentNull) => + value ?? throw new InvalidOperationException(message); /// /// Performs a general pre-condition check that fails if the specified value is . @@ -61,10 +58,8 @@ public static T CheckNotNull(T? value, string message = ArgumentNull) /// The underlying type of the value. /// Returns a non-null value of the specified type. /// If the specified value is . - public static T CheckNotNull(T? value, string message = ArgumentNull) where T : struct - { - return value ?? throw new InvalidOperationException(message); - } + public static T CheckNotNull(T? value, string message = ArgumentNull) where T : struct => + value ?? throw new InvalidOperationException(message); /// /// Performs a general pre-condition check that fails if the specified value is or an empty string. @@ -104,20 +99,6 @@ public static void Require(bool condition, string message = ArgumentFailed, stri if (!condition) throw new ArgumentException(message, parameterName); } - /// - /// Performs a general pre-condition requirement that fails when the specified condition is . - /// - /// The condition to check. - /// The exception message to throw in the event that the specified condition is . - /// The name of the invalid parameter. - /// If the specified condition is . - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This method is obsolete and will be removed in a future version. Use RequireWithinRangeInclusive or RequireWithinRangeExclusive methods instead.")] - public static void RequireWithinRange(bool condition, string message = ArgumentOutOfRange, string? parameterName = null) - { - if (!condition) throw new ArgumentOutOfRangeException(parameterName, message); - } - /// /// Performs a general pre-condition requirement that fails when the specified value falls inclusively outside the specified minimum and maximum values. /// @@ -157,10 +138,8 @@ public static void RequireWithinRangeExclusive(T value, T min, T max, string /// The underlying type of the value. /// Returns a non-null value of the specified type. /// If the specified value is . - public static T RequireNotNull(T? value, string message = ArgumentNull, string? parameterName = null) - { - return value ?? throw new ArgumentNullException(parameterName, message); - } + public static T RequireNotNull(T? value, string message = ArgumentNull, string? parameterName = null) => + value ?? throw new ArgumentNullException(parameterName, message); /// /// Performs a general pre-condition requirement that fails if the specified value is . @@ -171,10 +150,8 @@ public static T RequireNotNull(T? value, string message = ArgumentNull, strin /// The underlying type of the value. /// Returns a non-null value of the specified type. /// If the specified value is . - public static T RequireNotNull(T? value, string message = ArgumentNull, string? parameterName = null) where T : struct - { - return value ?? throw new ArgumentNullException(parameterName, message); - } + public static T RequireNotNull(T? value, string message = ArgumentNull, string? parameterName = null) where T : struct => + value ?? throw new ArgumentNullException(parameterName, message); /// /// Performs a general pre-condition requirement that fails if the specified value is or an empty string. diff --git a/OnixLabs.Core/Reflection/Extensions.Type.cs b/OnixLabs.Core/Reflection/Extensions.Type.cs index 377acc0..c4f3647 100644 --- a/OnixLabs.Core/Reflection/Extensions.Type.cs +++ b/OnixLabs.Core/Reflection/Extensions.Type.cs @@ -14,6 +14,8 @@ using System; using System.ComponentModel; +using System.Text; +using OnixLabs.Core.Text; namespace OnixLabs.Core.Reflection; @@ -23,15 +25,45 @@ namespace OnixLabs.Core.Reflection; [EditorBrowsable(EditorBrowsableState.Never)] public static class TypeExtensions { + private const char GenericTypeIdentifierMarker = '`'; + private const char GenericTypeOpenBracket = '<'; + private const char GenericTypeCloseBracket = '>'; + private const string GenericTypeSeparator = ", "; + private const string TypeNullExceptionMessage = "Type must not be null."; + /// - /// The identifier marker than indicates a generic type. + /// Gets the formatted type name from the current instance. /// - private const char GenericTypeIdentifierMarker = '`'; + /// The current instance from which to obtain the formatted type name. + /// The type name flags that will be used to format the type name. + /// Returns the formatted type name from the current instance. + public static string GetName(this Type type, TypeNameFlags flags = default) + { + RequireNotNull(type, TypeNullExceptionMessage, nameof(type)); + RequireIsDefined(flags, nameof(flags)); + + // ReSharper disable once HeapView.ObjectAllocation.Evident + StringBuilder builder = new(); + + builder.Append(type.GetName((flags & TypeNameFlags.UseFullNames) is not 0)); + + if (!type.IsGenericType || (flags & TypeNameFlags.UseGenericTypeArguments) is 0) + return builder.ToString(); + + builder.Append(GenericTypeOpenBracket); + + foreach (Type argument in type.GenericTypeArguments) + builder.Append(argument.GetName(flags)).Append(GenericTypeSeparator); + + return builder.TrimEnd(GenericTypeSeparator).Append(GenericTypeCloseBracket).ToString(); + } /// /// Gets the simple type name from the current instance. /// - /// The current instance from which to obtain the simple name. + /// The current instance from which to obtain the simple type name. + /// Determines whether the current 's full name or short name should be returned. /// Returns the simple type name from the current instance. - public static string GetName(this Type type) => type.Name.SubstringBeforeFirst(GenericTypeIdentifierMarker); + private static string GetName(this Type type, bool useFullName) => + (useFullName ? type.FullName ?? type.Name : type.Name).SubstringBeforeFirst(GenericTypeIdentifierMarker); } diff --git a/OnixLabs.Core/Reflection/TypeNameFlags.cs b/OnixLabs.Core/Reflection/TypeNameFlags.cs new file mode 100644 index 0000000..c2487af --- /dev/null +++ b/OnixLabs.Core/Reflection/TypeNameFlags.cs @@ -0,0 +1,44 @@ +// 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; + +namespace OnixLabs.Core.Reflection; + +/// +/// Specifies flags that control how a type name is formatted. +/// +[Flags] +public enum TypeNameFlags +{ + /// + /// Specifies that no type name flags are applied. + /// + None = default, + + /// + /// Specifies that type names should be formatted with their full name, where applicable. + /// + UseFullNames = 1, + + /// + /// Specifies that if a type is generic, it should be formatted with its generic type arguments. + /// + UseGenericTypeArguments = 2, + + /// + /// Specifies that type names should be formatted with their full name, where applicable, and that if a type is generic, it should be formatted with its generic type arguments. + /// + All = UseFullNames | UseGenericTypeArguments +} diff --git a/OnixLabs.Core/Result.Failure.cs b/OnixLabs.Core/Result.Failure.cs deleted file mode 100644 index bc645b5..0000000 --- a/OnixLabs.Core/Result.Failure.cs +++ /dev/null @@ -1,415 +0,0 @@ -// 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 System.Threading.Tasks; - -namespace OnixLabs.Core; - -/// -/// Represents a failed result. -/// -public sealed class Failure : Result, IValueEquatable -{ - /// - /// Initializes a new instance of the class. - /// - /// The underlying exception representing the failed result. - internal Failure(Exception exception) => Exception = exception; - - /// - /// Gets the underlying result exception. - /// - public Exception Exception { get; } - - /// - /// Creates a new instance of the class, where the underlying value represents a failed result. - /// - /// The underlying failed result exception. - /// - /// Returns a new instance of the class, where the underlying value represents a failed result. - /// - public static implicit operator Failure(Exception exception) => Failure(exception); - - /// - /// Performs an equality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Failure left, Failure right) => Equals(left, right); - - /// - /// Performs an inequality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Failure left, Failure right) => !Equals(left, right); - - /// - /// Checks whether the current object is equal to another object of the same type. - /// - /// An object to compare with the current object. - /// Returns if the current object is equal to the other parameter; otherwise, . - public bool Equals(Failure? other) => ReferenceEquals(this, other) || other is not null && Equals(other.Exception, Exception); - - /// - /// Checks for equality between the current instance and another object. - /// - /// The object to check for equality. - /// Returns if the object is equal to the current instance; otherwise, . - public override bool Equals(object? obj) => Equals(obj as Failure); - - /// - /// Serves as a hash code function for the current instance. - /// - /// Returns a hash code for the current instance. - public override int GetHashCode() => Exception.GetHashCode(); - - /// - /// Gets the underlying exception if the current is in a state, - /// or if the current is in a state. - /// - /// - /// Returns the underlying exception if the current is in a state, - /// or if the current is in a state. - /// - public override Exception GetExceptionOrDefault() => Exception; - - /// - /// Gets the underlying exception if the current is in a state, - /// or the specified default exception if the current is in a state. - /// - /// The default exception to return in the event that the current is in a state. - /// - /// Returns the underlying exception if the current is in a state, - /// or the specified default exception if the current is in a state. - /// - public override Exception GetExceptionOrDefault(Exception defaultException) => Exception; - - /// - /// Gets the underlying exception if the current is in a state, - /// or throws if the current is in a state. - /// - /// - /// Returns the underlying exception if the current is in a state, - /// or throws if the current is in a state. - /// - /// If the current is in a state. - public override Exception GetExceptionOrThrow() => Exception; - - /// - /// Executes the action that matches the value of the current instance. - /// In the case of a failure, the failure branch is invoked. - /// - /// The action to execute when the current instance is in a successful state. - /// The action to execute when the current instance is in a failed state. - public override void Match(Action? success = null, Action? failure = null) => failure?.Invoke(Exception); - - /// - /// Executes the function that matches the value of the current instance and returns its result. - /// In the case of a failure, the failure branch is invoked. - /// - /// The function to execute when the current instance is in a successful state. - /// The function to execute when the current instance is in a failed state. - /// The underlying type of the result produced by the matching function. - /// - /// Returns the result of the function if the current instance is in a successful state; - /// otherwise, returns the result of the function if the current instance is in a failed state. - /// - public override TResult Match(Func success, Func failure) => failure(Exception); - - /// - /// Applies the provided selector action to the value of the current instance. - /// In the case of a failure, the current instance is returned. - /// - /// The action to apply to current instance. - /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . - /// - public override Result Select(Action selector) => this; - - /// - /// Applies the provided selector function to the value of the current instance. - /// In the case of a failure, a new instance is returned with the current exception. - /// - /// The function to apply to the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public override Result Select(Func selector) => Result.Failure(Exception); - - /// - /// Applies the provided selector function to the value of the current instance. - /// In the case of a failure, the current instance is returned. - /// - /// The action to function to the current instance. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public override Result SelectMany(Func selector) => this; - - /// - /// Applies the provided selector function to the value of the current instance. - /// In the case of a failure, a new instance is returned with the current exception. - /// - /// The function to apply to the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public override Result SelectMany(Func> selector) => Result.Failure(Exception); - - /// - /// Throws the underlying exception if the current is in a failure state. - /// - public override void Throw() => throw Exception; - - /// - /// Returns a that represents the current object. - /// - /// Returns a that represents the current object. - public override string ToString() => Exception.ToString(); - - /// - /// Obtains a new instance containing the current exception. - /// - /// The underlying type of the to return. - /// Returns a new instance containing the current exception. - public Failure ToTypedResult() => Result.Failure(Exception); -} - -/// -/// Represents a failed result. -/// -/// The type of the underlying result value. -public sealed class Failure : Result, IValueEquatable> -{ - /// - /// Initializes a new instance of the class. - /// - /// The underlying exception representing the failed result. - internal Failure(Exception exception) => Exception = exception; - - /// - /// Gets the underlying result exception. - /// - public Exception Exception { get; } - - /// - /// Performs an equality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Failure left, Failure right) => Equals(left, right); - - /// - /// Performs an inequality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Failure left, Failure right) => !Equals(left, right); - - /// - /// Checks whether the current object is equal to another object of the same type. - /// - /// An object to compare with the current object. - /// Returns if the current object is equal to the other parameter; otherwise, . - public bool Equals(Failure? other) => ReferenceEquals(this, other) || other is not null && Equals(other.Exception, Exception); - - /// - /// Checks for equality between the current instance and another object. - /// - /// The object to check for equality. - /// Returns if the object is equal to the current instance; otherwise, . - public override bool Equals(object? obj) => Equals(obj as Failure); - - /// - /// Serves as a hash code function for the current instance. - /// - /// Returns a hash code for the current instance. - public override int GetHashCode() => Exception.GetHashCode(); - - /// - /// Gets the underlying exception if the current is in a state, - /// or if the current is in a state. - /// - /// - /// Returns the underlying exception if the current is in a state, - /// or if the current is in a state. - /// - public override Exception GetExceptionOrDefault() => Exception; - - /// - /// Gets the underlying exception if the current is in a state, - /// or the specified default exception if the current is in a state. - /// - /// The default exception to return in the event that the current is in a state. - /// - /// Returns the underlying exception if the current is in a state, - /// or the specified default exception if the current is in a state. - /// - public override Exception GetExceptionOrDefault(Exception defaultException) => Exception; - - /// - /// Gets the underlying exception if the current is in a state, - /// or throws if the current is in a state. - /// - /// - /// Returns the underlying exception if the current is in a state, - /// or throws if the current is in a state. - /// - /// If the current is in a state. - public override Exception GetExceptionOrThrow() => Exception; - - /// - /// Gets the underlying value of the current instance, if the underlying value is present; - /// otherwise returns the default value. - /// - /// - /// Returns the underlying value of the current instance, if the underlying value is present; - /// otherwise returns the default value. - /// - public override T? GetValueOrDefault() => default; - - /// - /// Gets the underlying value of the current instance, if the underlying value is present; - /// otherwise returns the specified default value. - /// - /// The default value to return in the event that the underlying value is absent. - /// - /// Returns the underlying value of the current instance, if the underlying value is present; - /// otherwise returns the specified default value. - /// - public override T GetValueOrDefault(T defaultValue) => defaultValue; - - /// - /// Gets the underlying value of the current instance; - /// otherwise throws the underlying exception if the current is in a failed stated. - /// - /// - /// Returns the underlying value of the current instance; - /// otherwise throws the underlying exception if the current is in a failed stated. - /// - public override T GetValueOrThrow() => throw Exception; - - /// - /// Executes the action that matches the value of the current instance. - /// In the case of a failure, the failure branch is invoked. - /// - /// The action to execute when the current instance is in a successful state. - /// The action to execute when the current instance is in a failed state. - public override void Match(Action? success = null, Action? failure = null) => failure?.Invoke(Exception); - - /// - /// Executes the function that matches the value of the current instance and returns its result. - /// In the case of a failure, the failure branch is invoked. - /// - /// The function to execute when the current instance is in a successful state. - /// The function to execute when the current instance is in a failed state. - /// The underlying type of the result produced by the matching function. - /// - /// Returns the result of the function if the current instance is in a successful state; - /// otherwise, returns the result of the function if the current instance is in a failed state. - /// - public override TResult Match(Func success, Func failure) => failure(Exception); - - /// - /// Applies the provided selector action to the value of the current instance. - /// In the case of a failure, a new instance is returned with the current exception. - /// - /// The action to apply to the value of the current instance. - /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . - /// - public override Result Select(Action selector) => Result.Failure(Exception); - - /// - /// Applies the provided selector function to the value of the current instance. - /// In the case of a failure, a new instance is returned with the current exception. - /// - /// The function to apply to the value of the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public override Result Select(Func selector) => Result.Failure(Exception); - - /// - /// Applies the provided selector function to the value of the current instance. - /// In the case of a failure, a new instance is returned with the current exception. - /// - /// The function to apply to the value of the current instance. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public override Result SelectMany(Func selector) => Result.Failure(Exception); - - /// - /// Applies the provided selector function to the value of the current instance. - /// In the case of a failure, a new instance is returned with the current exception. - /// - /// The function to apply to the value of the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public override Result SelectMany(Func> selector) => Result.Failure(Exception); - - /// - /// Throws the underlying exception if the current is in a failure state. - /// - public override void Throw() => throw Exception; - - /// - /// Returns a that represents the current object. - /// - /// Returns a that represents the current object. - public override string ToString() => Exception.ToString(); - - /// - /// Obtains a new instance containing the current exception. - /// - /// The underlying type of the to return. - /// Returns a new instance containing the current exception. - public Failure ToTypedResult() => Result.Failure(Exception); - - /// - /// Obtains a new instance containing the current exception. - /// - /// Returns a new instance containing the current exception. - public Failure ToUntypedResult() => Result.Failure(Exception); - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public override void Dispose() - { - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously. - /// - /// - /// Returns a task that represents the asynchronous dispose operation. - /// - public override async ValueTask DisposeAsync() - { - await ValueTask.CompletedTask; - } -} diff --git a/OnixLabs.Core/Result.Generic.cs b/OnixLabs.Core/Result.Generic.cs new file mode 100644 index 0000000..5145c10 --- /dev/null +++ b/OnixLabs.Core/Result.Generic.cs @@ -0,0 +1,486 @@ +// 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 System.Threading; +using System.Threading.Tasks; + +namespace OnixLabs.Core; + +/// +/// Represents a result value, which signifies the presence of a value or an exception. +/// +/// The type of the underlying result value. +public abstract class Result : IValueEquatable>, IDisposable, IAsyncDisposable +{ + // ReSharper disable once StaticMemberInGenericType, HeapView.ObjectAllocation.Evident + private static readonly InvalidOperationException UnrecognisedResultType = new("The type of the current result is unrecognised."); + + /// + /// Initializes a new instance of the class. + /// + internal Result() + { + } + + /// + /// Gets a value indicating whether the current is in a successful state. + /// + public bool IsSuccess => this is Success; + + /// + /// Gets a value indicating whether the current is in a failed state. + /// + public bool IsFailure => this is Failure; + + /// + /// Creates a new instance of the class, where the underlying value is the result of a successful invocation + /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. + /// + /// The function to invoke to obtain a successful or failed result. + /// + /// Returns a new instance of the class, where the underlying value is the result of a successful invocation + /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. + /// + public static Result Of(Func func) + { + try + { + return func(); + } + catch (Exception exception) + { + return exception; + } + } + + /// + /// Creates a new instance of the class, where the underlying value is the result of a successful invocation + /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. + /// + /// The function to invoke to obtain a successful or failed result. + /// The cancellation token that can be used to cancel long-running tasks. + /// + /// Returns a new instance of the class, where the underlying value is the result of a successful invocation + /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. + /// + public static async Task> OfAsync(Func> func, CancellationToken token = default) + { + try + { + return await func().WaitAsync(token).ConfigureAwait(false); + } + catch (Exception exception) + { + return exception; + } + } + + /// + /// Creates a new instance of the class, where the underlying value is the result of a successful invocation + /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. + /// + /// The function to invoke to obtain a successful or failed result. + /// The cancellation token to pass to the invoked function. + /// + /// Returns a new instance of the class, where the underlying value is the result of a successful invocation + /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. + /// + public static async Task> OfAsync(Func> func, CancellationToken token = default) + { + try + { + return await func(token).ConfigureAwait(false); + } + catch (Exception exception) + { + return exception; + } + } + + /// + /// Creates a new instance of the class, where the underlying value represents a successful result. + /// + /// The underlying successful result value. + /// + /// Returns a new instance of the class, where the underlying value represents a successful result. + /// + // ReSharper disable once HeapView.ObjectAllocation.Evident + public static Success Success(T value) => new(value); + + /// + /// Creates a new instance of the class, where the underlying exception represents a failed result. + /// + /// The underlying failed exception value. + /// + /// Returns a new instance of the class, where the underlying exception represents a failed result. + /// + // ReSharper disable once HeapView.ObjectAllocation.Evident + public static Failure Failure(Exception exception) => new(exception); + + /// + /// Creates a new instance of the class, where the underlying value represents a successful result. + /// + /// The underlying successful result value. + /// + /// Returns a new instance of the class, where the underlying value represents a successful result. + /// + public static implicit operator Result(T value) => Success(value); + + /// + /// Creates a new instance of the class, where the underlying value represents a failed result. + /// + /// The underlying failed result exception. + /// + /// Returns a new instance of the class, where the underlying value represents a failed result. + /// + public static implicit operator Result(Exception exception) => Failure(exception); + + /// + /// Gets the underlying value of the specified instance; + /// otherwise throws the underlying exception if the specified is in a failed stated. + /// + /// The value from which to obtain the underlying value. + /// + /// Returns the underlying value of the specified instance; + /// otherwise throws the underlying exception if the specified is in a failed stated. + /// + public static explicit operator T(Result value) => value.GetValueOrThrow(); + + /// + /// Performs an equality comparison between two object instances. + /// + /// The left-hand instance to compare. + /// The right-hand instance to compare. + /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . + public static bool operator ==(Result? left, Result? right) => Equals(left, right); + + /// + /// Performs an inequality comparison between two object instances. + /// + /// The left-hand instance to compare. + /// The right-hand instance to compare. + /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . + public static bool operator !=(Result? left, Result? right) => !Equals(left, right); + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + // ReSharper disable once HeapView.PossibleBoxingAllocation + if (this is Success { Value: IDisposable disposable }) + disposable.Dispose(); + + GC.SuppressFinalize(this); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously. + /// + /// + /// Returns a task that represents the asynchronous dispose operation. + /// + public async ValueTask DisposeAsync() + { + // ReSharper disable once HeapView.PossibleBoxingAllocation + if (this is Success { Value: IAsyncDisposable disposable }) + await disposable.DisposeAsync(); + + GC.SuppressFinalize(this); + } + + /// + /// Checks whether the current object is equal to another object of the same type. + /// + /// An object to compare with the current object. + /// Returns if the current object is equal to the other parameter; otherwise, . + public bool Equals(Result? other) => ResultEqualityComparer.Default.Equals(this, other); + + /// + /// Checks for equality between the current instance and another object. + /// + /// The object to check for equality. + /// Returns if the object is equal to the current instance; otherwise, . + public sealed override bool Equals(object? obj) => Equals(obj as Result); + + /// + /// Serves as a hash code function for the current instance. + /// + /// Returns a hash code for the current instance. + public sealed override int GetHashCode() => this switch + { + // ReSharper disable once HeapView.PossibleBoxingAllocation + Success success => success.Value?.GetHashCode() ?? default, + Failure failure => failure.Exception.GetHashCode(), + _ => throw UnrecognisedResultType + }; + + /// + /// Gets the underlying exception if the current is in a state, + /// or if the current is in a state. + /// + /// + /// Returns the underlying exception if the current is in a state, + /// or if the current is in a state. + /// + public Exception? GetExceptionOrDefault() => this is Failure failure ? failure.Exception : null; + + /// + /// Gets the underlying exception if the current is in a state, + /// or the specified default exception if the current is in a state. + /// + /// The default exception to return in the event that the current is in a state. + /// + /// Returns the underlying exception if the current is in a state, + /// or the specified default exception if the current is in a state. + /// + public Exception GetExceptionOrDefault(Exception defaultException) => this is Failure failure ? failure.Exception : defaultException; + + /// + /// Gets the underlying exception if the current is in a state, + /// or throws if the current is in a state. + /// + /// + /// Returns the underlying exception if the current is in a state, + /// or throws if the current is in a state. + /// + /// If the current is in a state. + public Exception GetExceptionOrThrow() => this is Failure failure ? failure.Exception : throw new InvalidOperationException("The current result is not in a failure state."); + + /// + /// Gets the underlying value of the current instance, if the underlying value is present; + /// otherwise returns the default value. + /// + /// + /// Returns the underlying value of the current instance, if the underlying value is present; + /// otherwise returns the default value. + /// + public T? GetValueOrDefault() => this is Success success ? success.Value : default; + + /// + /// Gets the underlying value of the current instance, if the underlying value is present; + /// otherwise returns the specified default value. + /// + /// The default value to return in the event that the underlying value is absent. + /// + /// Returns the underlying value of the current instance, if the underlying value is present; + /// otherwise returns the specified default value. + /// + public T GetValueOrDefault(T defaultValue) => this is Success success ? success.Value : defaultValue; + + /// + /// Gets the underlying value of the current instance; + /// otherwise throws the underlying exception if the current is in a failed stated. + /// + /// + /// Returns the underlying value of the current instance; + /// otherwise throws the underlying exception if the current is in a failed stated. + /// + public T GetValueOrThrow() => this is Success success ? success.Value : throw GetExceptionOrThrow(); + + /// + /// Executes the action that matches the value of the current instance. + /// + /// The action to execute when the current instance is in a successful state. + /// The action to execute when the current instance is in a failed state. + public void Match(Action? success = null, Action? failure = null) + { + switch (this) + { + case Success successResult: + success?.Invoke(successResult.Value); + break; + case Failure failureResult: + failure?.Invoke(failureResult.Exception); + break; + } + } + + /// + /// Executes the function that matches the value of the current instance and returns its result. + /// + /// The function to execute when the current instance is in a successful state. + /// The function to execute when the current instance is in a failed state. + /// The underlying type of the result produced by the matching function. + /// + /// Returns the result of the function if the current instance is in a successful state; + /// otherwise, returns the result of the function if the current instance is in a failed state. + /// + public TResult Match(Func success, Func failure) => this switch + { + Success successResult => success(successResult.Value), + Failure failureResult => failure(failureResult.Exception), + _ => throw UnrecognisedResultType + }; + + /// + /// Applies the provided selector action to the value of the current instance. + /// + /// The action to apply to the value of the current instance. + /// + /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . + /// + public Result Select(Action selector) + { + if (this is not Success success) return GetExceptionOrThrow(); + + try + { + selector(success.Value); + return Result.Success(); + } + catch (Exception exception) + { + return exception; + } + } + + /// + /// Applies the provided selector function to the value of the current instance. + /// + /// The function to apply to the value of the current instance. + /// The underlying type of the result produced by the selector function. + /// + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . + /// + public Result Select(Func selector) + { + if (this is not Success success) return GetExceptionOrThrow(); + + try + { + return selector(success.Value); + } + catch (Exception exception) + { + return exception; + } + } + + /// + /// Applies the provided selector function to the value of the current instance. + /// + /// The function to apply to the value of the current instance. + /// + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . + /// + public Result SelectMany(Func selector) + { + if (this is not Success success) return GetExceptionOrThrow(); + + try + { + return selector(success.Value); + } + catch (Exception exception) + { + return exception; + } + } + + /// + /// Applies the provided selector function to the value of the current instance. + /// + /// The function to apply to the value of the current instance. + /// The underlying type of the result produced by the selector function. + /// + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . + /// + public Result SelectMany(Func> selector) + { + if (this is not Success success) return GetExceptionOrThrow(); + + try + { + return selector(success.Value); + } + catch (Exception exception) + { + return exception; + } + } + + /// + /// Throws the underlying exception if the current is in a failure state. + /// Throwing the underlying exception from a location where it was not generated will yield an incorrect stack trace. + /// + public void Throw() + { + if (this is Failure failure) + throw failure.Exception; + } + + /// + /// Returns a that represents the current object. + /// + /// Returns a that represents the current object. + public sealed override string ToString() => this switch + { + // ReSharper disable once HeapView.PossibleBoxingAllocation + Success success => success.Value?.ToString() ?? string.Empty, + Failure failure => failure.Exception.Message, + _ => throw UnrecognisedResultType + }; +} + +/// +/// Represents a successful result. +/// +/// The type of the underlying result value. +public sealed class Success : Result +{ + /// + /// Initializes a new instance of the class. + /// + /// The underlying value representing the successful result. + internal Success(T value) => Value = value; + + /// + /// Gets the underlying result value. + /// + // ReSharper disable once MemberCanBePrivate.Global + public T Value { get; } +} + +/// +/// Represents a failed result. +/// +/// The type of the underlying result value. +public sealed class Failure : Result +{ + /// + /// Initializes a new instance of the class. + /// + /// The underlying exception representing the failed result. + internal Failure(Exception exception) => Exception = exception; + + /// + /// Gets the underlying result exception. + /// + public Exception Exception { get; } + + /// + /// Obtains a new instance containing the current exception. + /// + /// The underlying type of the to return. + /// Returns a new instance containing the current exception. + public Failure ToTypedResult() => Result.Failure(Exception); + + /// + /// Obtains a new instance containing the current exception. + /// + /// Returns a new instance containing the current exception. + public Failure ToUntypedResult() => Result.Failure(Exception); +} diff --git a/OnixLabs.Core/Result.Success.cs b/OnixLabs.Core/Result.Success.cs deleted file mode 100644 index 269dbdb..0000000 --- a/OnixLabs.Core/Result.Success.cs +++ /dev/null @@ -1,395 +0,0 @@ -// 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 System.Threading.Tasks; - -namespace OnixLabs.Core; - -/// -/// Represents a successful result. -/// -public sealed class Success : Result, IValueEquatable -{ - /// - /// Gets the singleton instance. - /// - public static readonly Success Instance = new(); - - /// - /// Initializes a new instance of the class. - /// - private Success() - { - } - - /// - /// Performs an equality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Success left, Success right) => Equals(left, right); - - /// - /// Performs an inequality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Success left, Success right) => !Equals(left, right); - - /// - /// Checks whether the current object is equal to another object of the same type. - /// - /// An object to compare with the current object. - /// Returns if the current object is equal to the other parameter; otherwise, . - public bool Equals(Success? other) => ReferenceEquals(this, Instance) && ReferenceEquals(other, Instance); - - /// - /// Checks for equality between the current instance and another object. - /// - /// The object to check for equality. - /// Returns if the object is equal to the current instance; otherwise, . - public override bool Equals(object? obj) => Equals(obj as Success); - - /// - /// Serves as a hash code function for the current instance. - /// - /// Returns a hash code for the current instance. - public override int GetHashCode() => default; - - /// - /// Gets the underlying exception if the current is in a state, - /// or if the current is in a state. - /// - /// - /// Returns the underlying exception if the current is in a state, - /// or if the current is in a state. - /// - public override Exception? GetExceptionOrDefault() => null; - - /// - /// Gets the underlying exception if the current is in a state, - /// or the specified default exception if the current is in a state. - /// - /// The default exception to return in the event that the current is in a state. - /// - /// Returns the underlying exception if the current is in a state, - /// or the specified default exception if the current is in a state. - /// - public override Exception GetExceptionOrDefault(Exception defaultException) => defaultException; - - /// - /// Gets the underlying exception if the current is in a state, - /// or throws if the current is in a state. - /// - /// - /// Returns the underlying exception if the current is in a state, - /// or throws if the current is in a state. - /// - /// If the current is in a state. - public override Exception GetExceptionOrThrow() => throw new InvalidOperationException("The current result is not in a Failure state."); - - /// - /// Executes the action that matches the value of the current instance. - /// In the case of a success, the success branch is invoked. - /// - /// The action to execute when the current instance is in a successful state. - /// The action to execute when the current instance is in a failed state. - public override void Match(Action? success = null, Action? failure = null) => success?.Invoke(); - - /// - /// Executes the function that matches the value of the current instance and returns its result. - /// In the case of a success, the success branch is invoked. - /// - /// The function to execute when the current instance is in a successful state. - /// The function to execute when the current instance is in a failed state. - /// The underlying type of the result produced by the matching function. - /// - /// Returns the result of the function if the current instance is in a successful state; - /// otherwise, returns the result of the function if the current instance is in a failed state. - /// - public override TResult Match(Func success, Func failure) => success(); - - /// - /// Applies the provided selector action to the value of the current instance. - /// In the case of a success, the result of the selector is wrapped into a new instance. - /// - /// The action to apply to current instance. - /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . - /// - public override Result Select(Action selector) => Of(selector); - - /// - /// Applies the provided selector function to the value of the current instance. - /// In the case of a success, the result of the selector is wrapped into a new instance. - /// - /// The function to apply to the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public override Result Select(Func selector) => Result.Of(selector); - - /// - /// Applies the provided selector function to the value of the current instance. - /// In the case of a success, the result of the selector is wrapped into a new instance. - /// - /// The function to apply to the current instance. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public override Result SelectMany(Func selector) => selector(); - - /// - /// Applies the provided selector function to the value of the current instance. - /// In the case of a success, the result of the selector is wrapped into a new instance. - /// - /// The function to apply to the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public override Result SelectMany(Func> selector) => selector(); - - /// - /// Throws the underlying exception if the current is in a failure state. - /// - public override void Throw() - { - } - - /// - /// Returns a that represents the current object. - /// - /// Returns a that represents the current object. - public override string ToString() => string.Empty; -} - -/// -/// Represents a successful result. -/// -/// The type of the underlying result value. -public sealed class Success : Result, IValueEquatable> -{ - /// - /// Initializes a new instance of the class. - /// - /// The underlying value representing the successful result. - internal Success(T value) => Value = value; - - /// - /// Gets the underlying result value. - /// - // ReSharper disable once MemberCanBePrivate.Global - public T Value { get; } - - /// - /// Performs an equality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Success left, Success right) => Equals(left, right); - - /// - /// Performs an inequality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Success left, Success right) => !Equals(left, right); - - /// - /// Checks whether the current object is equal to another object of the same type. - /// - /// An object to compare with the current object. - /// Returns if the current object is equal to the other parameter; otherwise, . - public bool Equals(Success? other) => ReferenceEquals(this, other) || other is not null && Equals(other.Value, Value); - - /// - /// Checks for equality between the current instance and another object. - /// - /// The object to check for equality. - /// Returns if the object is equal to the current instance; otherwise, . - public override bool Equals(object? obj) => Equals(obj as Success); - - /// - /// Serves as a hash code function for the current instance. - /// - /// Returns a hash code for the current instance. - public override int GetHashCode() => Value?.GetHashCode() ?? default; - - /// - /// Gets the underlying exception if the current is in a state, - /// or if the current is in a state. - /// - /// - /// Returns the underlying exception if the current is in a state, - /// or if the current is in a state. - /// - public override Exception? GetExceptionOrDefault() => null; - - /// - /// Gets the underlying exception if the current is in a state, - /// or the specified default exception if the current is in a state. - /// - /// The default exception to return in the event that the current is in a state. - /// - /// Returns the underlying exception if the current is in a state, - /// or the specified default exception if the current is in a state. - /// - public override Exception GetExceptionOrDefault(Exception defaultException) => defaultException; - - /// - /// Gets the underlying exception if the current is in a state, - /// or throws if the current is in a state. - /// - /// - /// Returns the underlying exception if the current is in a state, - /// or throws if the current is in a state. - /// - /// If the current is in a state. - public override Exception GetExceptionOrThrow() => throw new InvalidOperationException("The current result is not in a Failure state."); - - /// - /// Gets the underlying value of the current instance, if the underlying value is present; - /// otherwise returns the default value. - /// - /// - /// Returns the underlying value of the current instance, if the underlying value is present; - /// otherwise returns the default value. - /// - public override T GetValueOrDefault() => Value; - - /// - /// Gets the underlying value of the current instance, if the underlying value is present; - /// otherwise returns the specified default value. - /// - /// The default value to return in the event that the underlying value is absent. - /// - /// Returns the underlying value of the current instance, if the underlying value is present; - /// otherwise returns the specified default value. - /// - public override T GetValueOrDefault(T defaultValue) => Value ?? defaultValue; - - /// - /// Gets the underlying value of the current instance; - /// otherwise throws the underlying exception if the current is in a failed stated. - /// - /// - /// Returns the underlying value of the current instance; - /// otherwise throws the underlying exception if the current is in a failed stated. - /// - public override T GetValueOrThrow() => Value; - - /// - /// Executes the action that matches the value of the current instance. - /// In the case of a success, the success branch is invoked. - /// - /// The action to execute when the current instance is in a successful state. - /// The action to execute when the current instance is in a failed state. - public override void Match(Action? success = null, Action? failure = null) => success?.Invoke(Value); - - /// - /// Executes the function that matches the value of the current instance and returns its result. - /// In the case of a success, the success branch is invoked. - /// - /// The function to execute when the current instance is in a successful state. - /// The function to execute when the current instance is in a failed state. - /// The underlying type of the result produced by the matching function. - /// - /// Returns the result of the function if the current instance is in a successful state; - /// otherwise, returns the result of the function if the current instance is in a failed state. - /// - public override TResult Match(Func success, Func failure) => success(Value); - - /// - /// Applies the provided selector action to the value of the current instance. - /// In the case of a success, the result of the selector is wrapped into a new instance. - /// - /// The action to apply to current instance. - /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . - /// - public override Result Select(Action selector) => Result.Of(() => selector(Value)); - - /// - /// Applies the provided selector function to the value of the current instance. - /// In the case of a success, the result of the selector is wrapped into a new instance. - /// - /// The function to apply to the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public override Result Select(Func selector) => Result.Of(() => selector(Value)); - - /// - /// Applies the provided selector function to the value of the current instance. - /// In the case of a success, the result of the selector is wrapped into a new instance. - /// - /// The function to apply to the current instance. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public override Result SelectMany(Func selector) => selector(Value); - - /// - /// Applies the provided selector function to the value of the current instance. - /// In the case of a success, the result of the selector is wrapped into a new instance. - /// - /// The function to apply to the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public override Result SelectMany(Func> selector) => selector(Value); - - /// - /// Throws the underlying exception if the current is in a failure state. - /// - public override void Throw() - { - } - - /// - /// Returns a that represents the current object. - /// - /// Returns a that represents the current object. - public override string ToString() => Value?.ToString() ?? string.Empty; - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public override void Dispose() - { - if (Value is IDisposable disposable) - disposable.Dispose(); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously. - /// - /// - /// Returns a task that represents the asynchronous dispose operation. - /// - public override async ValueTask DisposeAsync() - { - if (Value is IAsyncDisposable disposable) - await disposable.DisposeAsync(); - } -} diff --git a/OnixLabs.Core/Result.cs b/OnixLabs.Core/Result.cs index 67a6182..c19263e 100644 --- a/OnixLabs.Core/Result.cs +++ b/OnixLabs.Core/Result.cs @@ -67,15 +67,16 @@ public static Result Of(Action action) /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. /// /// The function to invoke to obtain a successful or failed result. + /// The cancellation token that can be used to cancel long-running tasks. /// /// Returns a new instance of the class, where the underlying value is the result of a successful invocation /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. /// - public static async Task OfAsync(Func func) + public static async Task OfAsync(Func func, CancellationToken token = default) { try { - await func(); + await func().WaitAsync(token).ConfigureAwait(false); return Success(); } catch (Exception exception) @@ -98,7 +99,7 @@ public static async Task OfAsync(Func func, Can { try { - await func(token); + await func(token).ConfigureAwait(false); return Success(); } catch (Exception exception) @@ -123,7 +124,7 @@ public static async Task OfAsync(Func func, Can /// /// Returns a new instance of the class, where the underlying exception represents a failed result. /// - // ReSharper disable once MemberCanBePrivate.Global + // ReSharper disable once MemberCanBePrivate.Global, HeapView.ObjectAllocation.Evident public static Failure Failure(Exception exception) => new(exception); /// @@ -141,7 +142,7 @@ public static async Task OfAsync(Func func, Can /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Result left, Result right) => Equals(left, right); + public static bool operator ==(Result? left, Result? right) => Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -149,32 +150,35 @@ public static async Task OfAsync(Func func, Can /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Result left, Result right) => !Equals(left, right); + public static bool operator !=(Result? left, Result? right) => !Equals(left, right); /// /// Checks whether the current object is equal to another object of the same type. /// /// An object to compare with the current object. /// Returns if the current object is equal to the other parameter; otherwise, . - public bool Equals(Result? other) => this switch + public bool Equals(Result? other) { - Success success => other is Success successOther && success.Equals(successOther), - Failure failure => other is Failure failureOther && failure.Equals(failureOther), - _ => ReferenceEquals(this, other) - }; + if (ReferenceEquals(this, other)) return true; + + if (this is Failure thisFailure && other is Failure otherFailure) + return thisFailure.Exception == otherFailure.Exception; + + return this is Success && other is Success; + } /// /// Checks for equality between the current instance and another object. /// /// The object to check for equality. /// Returns if the object is equal to the current instance; otherwise, . - public override bool Equals(object? obj) => Equals(obj as Result); + public sealed override bool Equals(object? obj) => Equals(obj as Result); /// /// Serves as a hash code function for the current instance. /// /// Returns a hash code for the current instance. - public override int GetHashCode() => default; + public sealed override int GetHashCode() => this is Failure failure ? failure.Exception.GetHashCode() : default; /// /// Gets the underlying exception if the current is in a state, @@ -184,18 +188,18 @@ public static async Task OfAsync(Func func, Can /// Returns the underlying exception if the current is in a state, /// or if the current is in a state. /// - public abstract Exception? GetExceptionOrDefault(); + public Exception? GetExceptionOrDefault() => this is Failure failure ? failure.Exception : null; /// /// Gets the underlying exception if the current is in a state, /// or the specified default exception if the current is in a state. /// - /// The default exception to return in the event that the current is in a state. + /// The default exception to return in the event that the current is in a state. /// /// Returns the underlying exception if the current is in a state, /// or the specified default exception if the current is in a state. /// - public abstract Exception GetExceptionOrDefault(Exception defaultException); + public Exception GetExceptionOrDefault(Exception defaultValue) => this is Failure failure ? failure.Exception : defaultValue; /// /// Gets the underlying exception if the current is in a state, @@ -206,14 +210,25 @@ public static async Task OfAsync(Func func, Can /// or throws if the current is in a state. /// /// If the current is in a state. - public abstract Exception GetExceptionOrThrow(); + public Exception GetExceptionOrThrow() => this is Failure failure ? failure.Exception : throw new InvalidOperationException("The current result is not in a failure state."); /// /// Executes the action that matches the value of the current instance. /// /// The action to execute when the current instance is in a successful state. /// The action to execute when the current instance is in a failed state. - public abstract void Match(Action? success = null, Action? failure = null); + public void Match(Action? success = null, Action? failure = null) + { + switch (this) + { + case Failure failureResult: + failure?.Invoke(failureResult.Exception); + break; + default: + success?.Invoke(); + break; + } + } /// /// Executes the function that matches the value of the current instance and returns its result. @@ -225,7 +240,7 @@ public static async Task OfAsync(Func func, Can /// Returns the result of the function if the current instance is in a successful state; /// otherwise, returns the result of the function if the current instance is in a failed state. /// - public abstract TResult Match(Func success, Func failure); + public TResult Match(Func success, Func failure) => this is Failure failureResult ? failure(failureResult.Exception) : success(); /// /// Applies the provided selector action to the value of the current instance. @@ -234,26 +249,20 @@ public static async Task OfAsync(Func func, Can /// /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . /// - public abstract Result Select(Action selector); - - /// - /// Applies the provided selector function to the value of the current instance. - /// - /// The function to apply to the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public abstract Result Select(Func selector); + public Result Select(Action selector) + { + if (this is Failure) return this; - /// - /// Applies the provided selector function to the value of the current instance. - /// - /// The function to apply to the current instance. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public abstract Result SelectMany(Func selector); + try + { + selector(); + return Success(); + } + catch (Exception exception) + { + return exception; + } + } /// /// Applies the provided selector function to the value of the current instance. @@ -263,62 +272,13 @@ public static async Task OfAsync(Func func, Can /// /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// - public abstract Result SelectMany(Func> selector); - - /// - /// Throws the underlying exception if the current is in a failure state. - /// - public abstract void Throw(); - - /// - /// Returns a that represents the current object. - /// - /// Returns a that represents the current object. - public override string ToString() => this switch + public Result Select(Func selector) { - Success success => success.ToString(), - Failure failure => failure.ToString(), - _ => base.ToString() ?? GetType().FullName ?? nameof(Result) - }; -} + if (this is Failure failure) return failure.Exception; -/// -/// Represents a result value, which signifies the presence of a value or an exception. -/// -/// The type of the underlying result value. -public abstract class Result : IValueEquatable>, IDisposable, IAsyncDisposable -{ - /// - /// Initializes a new instance of the class. - /// - internal Result() - { - } - - /// - /// Gets a value indicating whether the current is in a successful state. - /// - public bool IsSuccess => this is Success; - - /// - /// Gets a value indicating whether the current is in a failed state. - /// - public bool IsFailure => this is Failure; - - /// - /// Creates a new instance of the class, where the underlying value is the result of a successful invocation - /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. - /// - /// The function to invoke to obtain a successful or failed result. - /// - /// Returns a new instance of the class, where the underlying value is the result of a successful invocation - /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. - /// - public static Result Of(Func func) - { try { - return func(); + return selector(); } catch (Exception exception) { @@ -327,19 +287,19 @@ public static Result Of(Func func) } /// - /// Creates a new instance of the class, where the underlying value is the result of a successful invocation - /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. + /// Applies the provided selector function to the value of the current instance. /// - /// The function to invoke to obtain a successful or failed result. + /// The function to apply to the current instance. /// - /// Returns a new instance of the class, where the underlying value is the result of a successful invocation - /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// - public static async Task> OfAsync(Func> func) + public Result SelectMany(Func selector) { + if (this is Failure) return this; + try { - return await func(); + return selector(); } catch (Exception exception) { @@ -348,20 +308,20 @@ public static async Task> OfAsync(Func> func) } /// - /// Creates a new instance of the class, where the underlying value is the result of a successful invocation - /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. + /// Applies the provided selector function to the value of the current instance. /// - /// The function to invoke to obtain a successful or failed result. - /// The cancellation token to pass to the invoked function. + /// The function to apply to the current instance. + /// The underlying type of the result produced by the selector function. /// - /// Returns a new instance of the class, where the underlying value is the result of a successful invocation - /// of the specified function; otherwise, the underlying value is the exception thrown by a failed invocation of the specified function. + /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . /// - public static async Task> OfAsync(Func> func, CancellationToken token = default) + public Result SelectMany(Func> selector) { + if (this is Failure failure) return failure.Exception; + try { - return await func(token); + return selector(); } catch (Exception exception) { @@ -370,239 +330,71 @@ public static async Task> OfAsync(Func> fun } /// - /// Creates a new instance of the class, where the underlying value represents a successful result. - /// - /// The underlying successful result value. - /// - /// Returns a new instance of the class, where the underlying value represents a successful result. - /// - public static Success Success(T value) => new(value); - - /// - /// Creates a new instance of the class, where the underlying exception represents a failed result. - /// - /// The underlying failed exception value. - /// - /// Returns a new instance of the class, where the underlying exception represents a failed result. - /// - public static Failure Failure(Exception exception) => new(exception); - - /// - /// Creates a new instance of the class, where the underlying value represents a successful result. - /// - /// The underlying successful result value. - /// - /// Returns a new instance of the class, where the underlying value represents a successful result. - /// - public static implicit operator Result(T value) => Success(value); - - /// - /// Creates a new instance of the class, where the underlying value represents a failed result. - /// - /// The underlying failed result exception. - /// - /// Returns a new instance of the class, where the underlying value represents a failed result. - /// - public static implicit operator Result(Exception exception) => Failure(exception); - - /// - /// Gets the underlying value of the specified instance; - /// otherwise throws the underlying exception if the specified is in a failed stated. - /// - /// The value from which to obtain the underlying value. - /// - /// Returns the underlying value of the specified instance; - /// otherwise throws the underlying exception if the specified is in a failed stated. - /// - public static explicit operator T(Result value) => value.GetValueOrThrow(); - - /// - /// Performs an equality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Result left, Result right) => Equals(left, right); - - /// - /// Performs an inequality comparison between two object instances. - /// - /// The left-hand instance to compare. - /// The right-hand instance to compare. - /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Result left, Result right) => !Equals(left, right); - - /// - /// Checks whether the current object is equal to another object of the same type. + /// Throws the underlying exception if the current is in a failure state. + /// Throwing the underlying exception from a location where it was not generated will yield an incorrect stack trace. /// - /// An object to compare with the current object. - /// Returns if the current object is equal to the other parameter; otherwise, . - public bool Equals(Result? other) => this switch + public void Throw() { - Success success => other is Success successOther && success.Equals(successOther), - Failure failure => other is Failure failureOther && failure.Equals(failureOther), - _ => ReferenceEquals(this, other) - }; - - /// - /// Checks for equality between the current instance and another object. - /// - /// The object to check for equality. - /// Returns if the object is equal to the current instance; otherwise, . - public override bool Equals(object? obj) => Equals(obj as Result); - - /// - /// Serves as a hash code function for the current instance. - /// - /// Returns a hash code for the current instance. - public override int GetHashCode() => default; - - /// - /// Gets the underlying exception if the current is in a state, - /// or if the current is in a state. - /// - /// - /// Returns the underlying exception if the current is in a state, - /// or if the current is in a state. - /// - public abstract Exception? GetExceptionOrDefault(); - - /// - /// Gets the underlying exception if the current is in a state, - /// or the specified default exception if the current is in a state. - /// - /// The default exception to return in the event that the current is in a state. - /// - /// Returns the underlying exception if the current is in a state, - /// or the specified default exception if the current is in a state. - /// - public abstract Exception GetExceptionOrDefault(Exception defaultException); - - /// - /// Gets the underlying exception if the current is in a state, - /// or throws if the current is in a state. - /// - /// - /// Returns the underlying exception if the current is in a state, - /// or throws if the current is in a state. - /// - /// If the current is in a state. - public abstract Exception GetExceptionOrThrow(); - - /// - /// Gets the underlying value of the current instance, if the underlying value is present; - /// otherwise returns the default value. - /// - /// - /// Returns the underlying value of the current instance, if the underlying value is present; - /// otherwise returns the default value. - /// - public abstract T? GetValueOrDefault(); - - /// - /// Gets the underlying value of the current instance, if the underlying value is present; - /// otherwise returns the specified default value. - /// - /// The default value to return in the event that the underlying value is absent. - /// - /// Returns the underlying value of the current instance, if the underlying value is present; - /// otherwise returns the specified default value. - /// - public abstract T GetValueOrDefault(T defaultValue); - - /// - /// Gets the underlying value of the current instance; - /// otherwise throws the underlying exception if the current is in a failed stated. - /// - /// - /// Returns the underlying value of the current instance; - /// otherwise throws the underlying exception if the current is in a failed stated. - /// - public abstract T GetValueOrThrow(); - - /// - /// Executes the action that matches the value of the current instance. - /// - /// The action to execute when the current instance is in a successful state. - /// The action to execute when the current instance is in a failed state. - public abstract void Match(Action? success = null, Action? failure = null); - - /// - /// Executes the function that matches the value of the current instance and returns its result. - /// - /// The function to execute when the current instance is in a successful state. - /// The function to execute when the current instance is in a failed state. - /// The underlying type of the result produced by the matching function. - /// - /// Returns the result of the function if the current instance is in a successful state; - /// otherwise, returns the result of the function if the current instance is in a failed state. - /// - public abstract TResult Match(Func success, Func failure); - - /// - /// Applies the provided selector action to the value of the current instance. - /// - /// The action to apply to the value of the current instance. - /// - /// Returns if the current is in a successful state, and the action invocation is also successful; otherwise; . - /// - public abstract Result Select(Action selector); + if (this is Failure failure) + throw failure.Exception; + } /// - /// Applies the provided selector function to the value of the current instance. + /// Returns a that represents the current object. /// - /// The function to apply to the value of the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public abstract Result Select(Func selector); + /// Returns a that represents the current object. + public sealed override string ToString() => this is Failure failure ? failure.Exception.Message : string.Empty; +} +/// +/// Represents a successful result. +/// +public sealed class Success : Result +{ /// - /// Applies the provided selector function to the value of the current instance. + /// Gets the singleton instance. /// - /// The function to apply to the value of the current instance. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public abstract Result SelectMany(Func selector); + // ReSharper disable once HeapView.ObjectAllocation.Evident + internal static readonly Success Instance = new(); /// - /// Applies the provided selector function to the value of the current instance. + /// Initializes a new instance of the class. /// - /// The function to apply to the value of the current instance. - /// The underlying type of the result produced by the selector function. - /// - /// Returns if the current is in a successful state, and the function invocation is also successful; otherwise; . - /// - public abstract Result SelectMany(Func> selector); + private Success() + { + } /// - /// Throws the underlying exception if the current is in a failure state. + /// Obtains a new instance containing the current exception. /// - public abstract void Throw(); + /// The underlying type of the to return. + /// Returns a new instance containing the current exception. + // ReSharper disable once MemberCanBeMadeStatic.Global +#pragma warning disable CA1822 + public Success ToTypedResult(TResult value) => Result.Success(value); +#pragma warning restore CA1822 +} +/// +/// Represents a failed result. +/// +public sealed class Failure : Result +{ /// - /// Returns a that represents the current object. + /// Initializes a new instance of the class. /// - /// Returns a that represents the current object. - public override string ToString() => this switch - { - Success success => success.ToString(), - Failure failure => failure.ToString(), - _ => base.ToString() ?? GetType().FullName ?? nameof(Result) - }; + /// The underlying exception representing the failed result. + internal Failure(Exception exception) => Exception = exception; /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// Gets the underlying result exception. /// - public abstract void Dispose(); + public Exception Exception { get; } /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously. + /// Obtains a new instance containing the current exception. /// - /// - /// Returns a task that represents the asynchronous dispose operation. - /// - public abstract ValueTask DisposeAsync(); + /// The underlying type of the to return. + /// Returns a new instance containing the current exception. + public Failure ToTypedResult() => Result.Failure(Exception); } diff --git a/OnixLabs.Core/ResultEqualityComparer.cs b/OnixLabs.Core/ResultEqualityComparer.cs index 6d3cea5..1ce0625 100644 --- a/OnixLabs.Core/ResultEqualityComparer.cs +++ b/OnixLabs.Core/ResultEqualityComparer.cs @@ -13,6 +13,7 @@ // limitations under the License. using System.Collections.Generic; +using OnixLabs.Core.Collections.Generic; namespace OnixLabs.Core; @@ -21,14 +22,21 @@ namespace OnixLabs.Core; /// /// The that will be used to compare the underlying values of each instance. /// The underlying type of the instance. -public sealed class ResultEqualityComparer(EqualityComparer? valueComparer = null) : EqualityComparer> where T : notnull +public sealed class ResultEqualityComparer(EqualityComparer? valueComparer = null) : IEqualityComparer> { + /// + /// Gets the default instance. + /// + // ReSharper disable once HeapView.ObjectAllocation.Evident + public static readonly ResultEqualityComparer Default = new(); + /// Determines whether the specified values are equal. /// The first object of type to compare. /// The second object of type to compare. /// Returns if the specified values are equal; otherwise, . - public override bool Equals(Result? x, Result? y) + public bool Equals(Result? x, Result? y) { + if (ReferenceEquals(x, y)) return true; if (x is null || y is null) return x is null && y is null; if (x is Failure xFailure && y is Failure yFailure) @@ -37,11 +45,11 @@ public override bool Equals(Result? x, Result? y) T? xValue = x.GetValueOrDefault(); T? yValue = y.GetValueOrDefault(); - return (valueComparer ?? EqualityComparer.Default).Equals(xValue, yValue); + return valueComparer.GetOrDefault().Equals(xValue, yValue); } /// Returns a hash code for the specified object. /// The for which a hash code is to be returned. /// A hash code for the specified object. - public override int GetHashCode(Result obj) => obj.GetHashCode(); + public int GetHashCode(Result obj) => obj.GetHashCode(); } diff --git a/OnixLabs.Core/Text/Base16.Equatable.cs b/OnixLabs.Core/Text/Base16.Equatable.cs index f3dfbf2..e8af4aa 100644 --- a/OnixLabs.Core/Text/Base16.Equatable.cs +++ b/OnixLabs.Core/Text/Base16.Equatable.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Collections.Generic; using OnixLabs.Core.Linq; namespace OnixLabs.Core.Text; @@ -44,7 +45,7 @@ public readonly partial struct Base16 /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Base16 left, Base16 right) => Equals(left, right); + public static bool operator ==(Base16 left, Base16 right) => EqualityComparer.Default.Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -52,5 +53,5 @@ public readonly partial struct Base16 /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Base16 left, Base16 right) => !Equals(left, right); + public static bool operator !=(Base16 left, Base16 right) => !EqualityComparer.Default.Equals(left, right); } diff --git a/OnixLabs.Core/Text/Base16.To.cs b/OnixLabs.Core/Text/Base16.To.cs index 75c1e10..c675d9c 100644 --- a/OnixLabs.Core/Text/Base16.To.cs +++ b/OnixLabs.Core/Text/Base16.To.cs @@ -24,6 +24,12 @@ public readonly partial struct Base16 /// Return the underlying representation of the current instance. public byte[] ToByteArray() => value.Copy(); + /// + /// Gets the underlying representation of the current instance as a new instance. + /// + /// Return the underlying representation of the current instance as a new instance. + public ReadOnlySpan ToReadOnlySpan() => value; + /// /// Returns a that represents the current object. /// diff --git a/OnixLabs.Core/Text/Base16.cs b/OnixLabs.Core/Text/Base16.cs index 9267c6a..a083964 100644 --- a/OnixLabs.Core/Text/Base16.cs +++ b/OnixLabs.Core/Text/Base16.cs @@ -28,6 +28,7 @@ public readonly partial struct Base16(ReadOnlySpan value) : IBaseValue struct. /// /// The with which to initialize the instance. + // ReSharper disable once MemberCanBePrivate.Global public Base16(ReadOnlySequence value) : this(ReadOnlySpan.Empty) => value.CopyTo(out this.value); /// @@ -35,6 +36,7 @@ public readonly partial struct Base16(ReadOnlySpan value) : IBaseValue /// The with which to initialize the instance. /// The which will be used to obtain the underlying value. + // ReSharper disable once MemberCanBePrivate.Global public Base16(string value, Encoding? encoding = null) : this(encoding.GetOrDefault().GetBytes(value)) { } @@ -44,6 +46,7 @@ public Base16(string value, Encoding? encoding = null) : this(encoding.GetOrDefa /// /// The with which to initialize the instance. /// The which will be used to obtain the underlying value. + // ReSharper disable once MemberCanBePrivate.Global public Base16(char[] value, Encoding? encoding = null) : this(encoding.GetOrDefault().GetBytes(value)) { } @@ -53,6 +56,7 @@ public Base16(char[] value, Encoding? encoding = null) : this(encoding.GetOrDefa /// /// The with which to initialize the instance. /// The which will be used to obtain the underlying value. + // ReSharper disable once MemberCanBePrivate.Global public Base16(ReadOnlySequence value, Encoding? encoding = null) : this(encoding.GetOrDefault().GetBytes(value)) { } diff --git a/OnixLabs.Core/Text/Base16FormatProvider.cs b/OnixLabs.Core/Text/Base16FormatProvider.cs index e259c1d..2d677f0 100644 --- a/OnixLabs.Core/Text/Base16FormatProvider.cs +++ b/OnixLabs.Core/Text/Base16FormatProvider.cs @@ -19,6 +19,7 @@ namespace OnixLabs.Core.Text; /// /// Represents a Base-16 format provider. /// +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed class Base16FormatProvider : Enumeration, IFormatProvider { /// diff --git a/OnixLabs.Core/Text/Base32.Equatable.cs b/OnixLabs.Core/Text/Base32.Equatable.cs index 63e2d95..f99aebc 100644 --- a/OnixLabs.Core/Text/Base32.Equatable.cs +++ b/OnixLabs.Core/Text/Base32.Equatable.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Linq; +using System.Collections.Generic; using OnixLabs.Core.Linq; namespace OnixLabs.Core.Text; @@ -45,7 +45,7 @@ public readonly partial struct Base32 /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Base32 left, Base32 right) => Equals(left, right); + public static bool operator ==(Base32 left, Base32 right) => EqualityComparer.Default.Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -53,5 +53,5 @@ public readonly partial struct Base32 /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Base32 left, Base32 right) => !Equals(left, right); + public static bool operator !=(Base32 left, Base32 right) => !EqualityComparer.Default.Equals(left, right); } diff --git a/OnixLabs.Core/Text/Base32.To.cs b/OnixLabs.Core/Text/Base32.To.cs index fc21b64..e553c06 100644 --- a/OnixLabs.Core/Text/Base32.To.cs +++ b/OnixLabs.Core/Text/Base32.To.cs @@ -24,6 +24,12 @@ public readonly partial struct Base32 /// Return the underlying representation of the current instance. public byte[] ToByteArray() => value.Copy(); + /// + /// Gets the underlying representation of the current instance as a new instance. + /// + /// Return the underlying representation of the current instance as a new instance. + public ReadOnlySpan ToReadOnlySpan() => value; + /// /// Returns a that represents the current object. /// diff --git a/OnixLabs.Core/Text/Base32.cs b/OnixLabs.Core/Text/Base32.cs index 184daa6..bd21b60 100644 --- a/OnixLabs.Core/Text/Base32.cs +++ b/OnixLabs.Core/Text/Base32.cs @@ -28,6 +28,7 @@ public readonly partial struct Base32(ReadOnlySpan value) : IBaseValue struct. /// /// The with which to initialize the instance. + // ReSharper disable once MemberCanBePrivate.Global public Base32(ReadOnlySequence value) : this(ReadOnlySpan.Empty) => value.CopyTo(out this.value); /// @@ -35,6 +36,7 @@ public readonly partial struct Base32(ReadOnlySpan value) : IBaseValue /// The with which to initialize the instance. /// The which will be used to obtain the underlying value. + // ReSharper disable once MemberCanBePrivate.Global public Base32(string value, Encoding? encoding = null) : this(encoding.GetOrDefault().GetBytes(value)) { } @@ -44,6 +46,7 @@ public Base32(string value, Encoding? encoding = null) : this(encoding.GetOrDefa /// /// The with which to initialize the instance. /// The which will be used to obtain the underlying value. + // ReSharper disable once MemberCanBePrivate.Global public Base32(char[] value, Encoding? encoding = null) : this(encoding.GetOrDefault().GetBytes(value)) { } @@ -53,6 +56,7 @@ public Base32(char[] value, Encoding? encoding = null) : this(encoding.GetOrDefa /// /// The with which to initialize the instance. /// The which will be used to obtain the underlying value. + // ReSharper disable once MemberCanBePrivate.Global public Base32(ReadOnlySequence value, Encoding? encoding = null) : this(encoding.GetOrDefault().GetBytes(value)) { } diff --git a/OnixLabs.Core/Text/Base32Codec.cs b/OnixLabs.Core/Text/Base32Codec.cs index d19b673..b287bac 100644 --- a/OnixLabs.Core/Text/Base32Codec.cs +++ b/OnixLabs.Core/Text/Base32Codec.cs @@ -82,6 +82,7 @@ public bool TryEncode(ReadOnlySpan value, IFormatProvider? provider, out s return false; } + // ReSharper disable once HeapView.ObjectAllocation.Evident StringBuilder builder = new(value.Length * InputSize / OutputSize); int inputPosition = 0; @@ -168,6 +169,7 @@ public bool TryDecode(ReadOnlySpan value, IFormatProvider? provider, out b ReadOnlySpan valueWithoutPadding = formatProvider.IsPadded ? value.TrimEnd('=') : value; + // ReSharper disable once HeapView.ObjectAllocation.Evident byte[] outputBytes = new byte[valueWithoutPadding.Length * OutputSize / InputSize]; if (outputBytes.Length == 0) diff --git a/OnixLabs.Core/Text/Base32FormatProvider.cs b/OnixLabs.Core/Text/Base32FormatProvider.cs index a0e0d02..70052e6 100644 --- a/OnixLabs.Core/Text/Base32FormatProvider.cs +++ b/OnixLabs.Core/Text/Base32FormatProvider.cs @@ -19,6 +19,7 @@ namespace OnixLabs.Core.Text; /// /// Represents a Base-32 format provider. /// +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed class Base32FormatProvider : Enumeration, IFormatProvider { /// diff --git a/OnixLabs.Core/Text/Base58.Equatable.cs b/OnixLabs.Core/Text/Base58.Equatable.cs index a9a1a97..4089854 100644 --- a/OnixLabs.Core/Text/Base58.Equatable.cs +++ b/OnixLabs.Core/Text/Base58.Equatable.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Linq; +using System.Collections.Generic; using OnixLabs.Core.Linq; namespace OnixLabs.Core.Text; @@ -45,7 +45,7 @@ public readonly partial struct Base58 /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Base58 left, Base58 right) => Equals(left, right); + public static bool operator ==(Base58 left, Base58 right) => EqualityComparer.Default.Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -53,5 +53,5 @@ public readonly partial struct Base58 /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Base58 left, Base58 right) => !Equals(left, right); + public static bool operator !=(Base58 left, Base58 right) => !EqualityComparer.Default.Equals(left, right); } diff --git a/OnixLabs.Core/Text/Base58.To.cs b/OnixLabs.Core/Text/Base58.To.cs index aac8ee7..6231994 100644 --- a/OnixLabs.Core/Text/Base58.To.cs +++ b/OnixLabs.Core/Text/Base58.To.cs @@ -24,6 +24,12 @@ public readonly partial struct Base58 /// Return the underlying representation of the current instance. public byte[] ToByteArray() => value.Copy(); + /// + /// Gets the underlying representation of the current instance as a new instance. + /// + /// Return the underlying representation of the current instance as a new instance. + public ReadOnlySpan ToReadOnlySpan() => value; + /// /// Returns a that represents the current object. /// diff --git a/OnixLabs.Core/Text/Base58.cs b/OnixLabs.Core/Text/Base58.cs index 8f33bcb..2825d0e 100644 --- a/OnixLabs.Core/Text/Base58.cs +++ b/OnixLabs.Core/Text/Base58.cs @@ -28,6 +28,7 @@ public readonly partial struct Base58(ReadOnlySpan value) : IBaseValue struct. /// /// The with which to initialize the instance. + // ReSharper disable once MemberCanBePrivate.Global public Base58(ReadOnlySequence value) : this(ReadOnlySpan.Empty) => value.CopyTo(out this.value); /// @@ -35,6 +36,7 @@ public readonly partial struct Base58(ReadOnlySpan value) : IBaseValue /// The with which to initialize the instance. /// The which will be used to obtain the underlying value. + // ReSharper disable once MemberCanBePrivate.Global public Base58(string value, Encoding? encoding = null) : this(encoding.GetOrDefault().GetBytes(value)) { } @@ -44,6 +46,7 @@ public Base58(string value, Encoding? encoding = null) : this(encoding.GetOrDefa /// /// The with which to initialize the instance. /// The which will be used to obtain the underlying value. + // ReSharper disable once MemberCanBePrivate.Global public Base58(char[] value, Encoding? encoding = null) : this(encoding.GetOrDefault().GetBytes(value)) { } @@ -53,6 +56,7 @@ public Base58(char[] value, Encoding? encoding = null) : this(encoding.GetOrDefa /// /// The with which to initialize the instance. /// The which will be used to obtain the underlying value. + // ReSharper disable once MemberCanBePrivate.Global public Base58(ReadOnlySequence value, Encoding? encoding = null) : this(encoding.GetOrDefault().GetBytes(value)) { } diff --git a/OnixLabs.Core/Text/Base58Codec.cs b/OnixLabs.Core/Text/Base58Codec.cs index 16cde82..909c651 100644 --- a/OnixLabs.Core/Text/Base58Codec.cs +++ b/OnixLabs.Core/Text/Base58Codec.cs @@ -75,6 +75,7 @@ public bool TryEncode(ReadOnlySpan value, IFormatProvider? provider, out s return false; } + // ReSharper disable once HeapView.ObjectAllocation.Evident StringBuilder builder = new(); BigInteger data = BigInteger.Zero; diff --git a/OnixLabs.Core/Text/Base58FormatProvider.cs b/OnixLabs.Core/Text/Base58FormatProvider.cs index 7e97486..b38ceb0 100644 --- a/OnixLabs.Core/Text/Base58FormatProvider.cs +++ b/OnixLabs.Core/Text/Base58FormatProvider.cs @@ -19,6 +19,7 @@ namespace OnixLabs.Core.Text; /// /// Represents a Base-58 format provider. /// +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed class Base58FormatProvider : Enumeration, IFormatProvider { /// diff --git a/OnixLabs.Core/Text/Base64.Equatable.cs b/OnixLabs.Core/Text/Base64.Equatable.cs index 63a904b..b102b13 100644 --- a/OnixLabs.Core/Text/Base64.Equatable.cs +++ b/OnixLabs.Core/Text/Base64.Equatable.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Linq; +using System.Collections.Generic; using OnixLabs.Core.Linq; namespace OnixLabs.Core.Text; @@ -45,7 +45,7 @@ public readonly partial struct Base64 /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Base64 left, Base64 right) => Equals(left, right); + public static bool operator ==(Base64 left, Base64 right) => EqualityComparer.Default.Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -53,5 +53,5 @@ public readonly partial struct Base64 /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Base64 left, Base64 right) => !Equals(left, right); + public static bool operator !=(Base64 left, Base64 right) => !EqualityComparer.Default.Equals(left, right); } diff --git a/OnixLabs.Core/Text/Base64.To.cs b/OnixLabs.Core/Text/Base64.To.cs index fa67955..a8a4c53 100644 --- a/OnixLabs.Core/Text/Base64.To.cs +++ b/OnixLabs.Core/Text/Base64.To.cs @@ -24,6 +24,12 @@ public readonly partial struct Base64 /// Return the underlying representation of the current instance. public byte[] ToByteArray() => value.Copy(); + /// + /// Gets the underlying representation of the current instance as a new instance. + /// + /// Return the underlying representation of the current instance as a new instance. + public ReadOnlySpan ToReadOnlySpan() => value; + /// /// Returns a that represents the current object. /// diff --git a/OnixLabs.Core/Text/Base64.cs b/OnixLabs.Core/Text/Base64.cs index 1cc5d94..916cda2 100644 --- a/OnixLabs.Core/Text/Base64.cs +++ b/OnixLabs.Core/Text/Base64.cs @@ -28,6 +28,7 @@ public readonly partial struct Base64(ReadOnlySpan value) : IBaseValue struct. /// /// The with which to initialize the instance. + // ReSharper disable once MemberCanBePrivate.Global public Base64(ReadOnlySequence value) : this(ReadOnlySpan.Empty) => value.CopyTo(out this.value); /// @@ -35,6 +36,7 @@ public readonly partial struct Base64(ReadOnlySpan value) : IBaseValue /// The with which to initialize the instance. /// The which will be used to obtain the underlying value. + // ReSharper disable once MemberCanBePrivate.Global public Base64(string value, Encoding? encoding = null) : this(encoding.GetOrDefault().GetBytes(value)) { } @@ -44,6 +46,7 @@ public Base64(string value, Encoding? encoding = null) : this(encoding.GetOrDefa /// /// The with which to initialize the instance. /// The which will be used to obtain the underlying value. + // ReSharper disable once MemberCanBePrivate.Global public Base64(char[] value, Encoding? encoding = null) : this(encoding.GetOrDefault().GetBytes(value)) { } @@ -53,6 +56,7 @@ public Base64(char[] value, Encoding? encoding = null) : this(encoding.GetOrDefa /// /// The with which to initialize the instance. /// The which will be used to obtain the underlying value. + // ReSharper disable once MemberCanBePrivate.Global public Base64(ReadOnlySequence value, Encoding? encoding = null) : this(encoding.GetOrDefault().GetBytes(value)) { } diff --git a/OnixLabs.Core/Text/Base64FormatProvider.cs b/OnixLabs.Core/Text/Base64FormatProvider.cs index 9b586c6..c5d8bb0 100644 --- a/OnixLabs.Core/Text/Base64FormatProvider.cs +++ b/OnixLabs.Core/Text/Base64FormatProvider.cs @@ -19,6 +19,7 @@ namespace OnixLabs.Core.Text; /// /// Represents a Base-64 format provider. /// +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed class Base64FormatProvider : Enumeration, IFormatProvider { /// diff --git a/OnixLabs.Core/Text/Extensions.StringBuilder.cs b/OnixLabs.Core/Text/Extensions.StringBuilder.cs index d2424cf..c4b351b 100644 --- a/OnixLabs.Core/Text/Extensions.StringBuilder.cs +++ b/OnixLabs.Core/Text/Extensions.StringBuilder.cs @@ -14,7 +14,6 @@ using System.ComponentModel; using System.Text; -using OnixLabs.Core.Linq; namespace OnixLabs.Core.Text; @@ -33,7 +32,7 @@ public static class StringBuilderExtensions /// The to append to. /// The values to append. /// Returns the current with the specified values appended. - public static StringBuilder Append(this StringBuilder builder, params object[] values) => builder.Append(values.JoinToString(string.Empty)); + public static StringBuilder Append(this StringBuilder builder, params object[] values) => builder.AppendJoin(string.Empty, values); /// /// Appends the specified value, prefixed with the escape sequence to the current . @@ -43,13 +42,47 @@ public static class StringBuilderExtensions /// Returns the current with the escape sequence and specified value appended. internal static StringBuilder AppendEscaped(this StringBuilder builder, char value) => builder.Append(EscapeSequence).Append(value); + /// + /// Prepends the specified value to the current + /// + /// The to prepend to. + /// The value to prepend. + /// Returns the current with the specified values prepended. + public static StringBuilder Prepend(this StringBuilder builder, object value) => builder.Insert(0, value); + + /// + /// Prepends the specified value to the current + /// + /// The to prepend to. + /// The value to prepend. + /// Returns the current with the specified values prepended. + public static StringBuilder Prepend(this StringBuilder builder, char value) => builder.Insert(0, value); + + /// + /// Prepends the specified value to the current + /// + /// The to prepend to. + /// The value to prepend. + /// Returns the current with the specified values prepended. + public static StringBuilder Prepend(this StringBuilder builder, string value) => builder.Insert(0, value); + /// /// Prepends the specified values to the current . /// /// The to prepend to. /// The values to prepend. /// Returns the current with the specified values prepended. - public static StringBuilder Prepend(this StringBuilder builder, params object[] values) => builder.Insert(0, values.JoinToString(string.Empty)); + public static StringBuilder Prepend(this StringBuilder builder, params object[] values) => builder.PrependJoin(string.Empty, values); + + /// + /// Concatenates the string representations of the elements in the provided array of objects, using the specified separator between each member, then prepends the result to the current instance of the string builder. + /// + /// The to prepend to. + /// The string to use as a separator. is included in the joined strings only if has more than one element. + /// An array that contains the strings to concatenate and append to the current instance of the string builder. + /// Returns the current with the specified values joined and prepended. + public static StringBuilder PrependJoin(this StringBuilder builder, string separator, params object?[] values) => + builder.Prepend(string.Join(separator, values)); /// /// Trims the specified value from the start and end of the current . @@ -103,6 +136,8 @@ public static StringBuilder TrimStart(this StringBuilder builder, char value) /// Returns the current with the specified value trimmed from the end. public static StringBuilder TrimEnd(this StringBuilder builder, string value) { + if (string.IsNullOrEmpty(value)) return builder; + while (builder.Length >= value.Length && builder.ToString(builder.Length - value.Length, value.Length) == value) builder.Remove(builder.Length - value.Length, value.Length); @@ -117,6 +152,8 @@ public static StringBuilder TrimEnd(this StringBuilder builder, string value) /// Returns the current with the specified value trimmed from the start. public static StringBuilder TrimStart(this StringBuilder builder, string value) { + if (string.IsNullOrEmpty(value)) return builder; + while (builder.Length >= value.Length && builder.ToString(0, value.Length) == value) builder.Remove(0, value.Length); diff --git a/OnixLabs.Core/Text/IBaseCodec.cs b/OnixLabs.Core/Text/IBaseCodec.cs index 37c92da..e399345 100644 --- a/OnixLabs.Core/Text/IBaseCodec.cs +++ b/OnixLabs.Core/Text/IBaseCodec.cs @@ -35,21 +35,25 @@ public interface IBaseCodec /// /// Gets a new instance. /// + // ReSharper disable once HeapView.ObjectAllocation.Evident public static Base16Codec Base16 => new(); /// /// Gets a new instance. /// + // ReSharper disable once HeapView.ObjectAllocation.Evident public static Base32Codec Base32 => new(); /// /// Gets a new instance. /// + // ReSharper disable once HeapView.ObjectAllocation.Evident public static Base58Codec Base58 => new(); /// /// Gets a new instance. /// + // ReSharper disable once HeapView.ObjectAllocation.Evident public static Base64Codec Base64 => new(); /// diff --git a/OnixLabs.Core/Text/IBaseValue.cs b/OnixLabs.Core/Text/IBaseValue.cs index 103c244..f6d15c8 100644 --- a/OnixLabs.Core/Text/IBaseValue.cs +++ b/OnixLabs.Core/Text/IBaseValue.cs @@ -19,7 +19,7 @@ namespace OnixLabs.Core.Text; /// /// Defines a Base-N value. /// -public interface IBaseValue : IBinaryConvertible, ISpanFormattable +public interface IBaseValue : ISpanBinaryConvertible, ISpanFormattable { /// /// Formats the value of the current instance using the specified format. diff --git a/OnixLabs.Numerics.UnitTests.Data/NumericDataProviders/FullNumericDataProvider.cs b/OnixLabs.Numerics.UnitTests.Data/NumericDataProviders/FullNumericDataProvider.cs new file mode 100644 index 0000000..85260e6 --- /dev/null +++ b/OnixLabs.Numerics.UnitTests.Data/NumericDataProviders/FullNumericDataProvider.cs @@ -0,0 +1,894 @@ +// 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.Globalization; + +namespace OnixLabs.Numerics.UnitTests.Data.NumericDataProviders; + +internal sealed class FullNumericDataProvider : INumericDataProvider +{ + public int MinScale => 0; + public int MaxScale => 28; + public int RandomCount => 100; + + public int[] IntegerValues => + [ + 0, + + +1, +2, +3, +4, +5, +6, +7, +8, +9, + +10, +100, +1_000, +10_000, +1_000_000_000, + +12, +123, +1_234, +12_345, +1_234_567_890, + + -1, -2, -3, -4, -5, -6, -7, -8, -9, + -10, -100, -1_000, -10_000, -1_000_000_000, + -12, -123, -1_234, -12_345, -1_234_567_890 + ]; + + public int[] IntegerScales => [0, 1, 2, 3, 4, 5, 10]; + + public decimal[] DecimalValues => + [ + 0m, + 0.0m, + 0.00m, + 0.000m, + 0.0000m, + 0.00000m, + 0.000000m, + 0.0000000m, + 0.00000000m, + 0.000000000m, + 0.0000000000m, + 0.00000000000m, + 0.000000000000m, + 0.0000000000000m, + 0.00000000000000m, + 0.000000000000000m, + 0.0000000000000000m, + 0.00000000000000000m, + 0.000000000000000000m, + 0.0000000000000000000m, + 0.00000000000000000000m, + 0.000000000000000000000m, + 0.0000000000000000000000m, + 0.00000000000000000000000m, + 0.000000000000000000000000m, + 0.0000000000000000000000000m, + 0.00000000000000000000000000m, + 0.000000000000000000000000000m, + 1m, + 0.1m, + 0.01m, + 0.001m, + 0.0001m, + 0.00001m, + 0.000001m, + 0.0000001m, + 0.00000001m, + 0.000000001m, + 0.0000000001m, + 0.00000000001m, + 0.000000000001m, + 0.0000000000001m, + 0.00000000000001m, + 0.000000000000001m, + 0.0000000000000001m, + 0.00000000000000001m, + 0.000000000000000001m, + 0.0000000000000000001m, + 0.00000000000000000001m, + 0.000000000000000000001m, + 0.0000000000000000000001m, + 0.00000000000000000000001m, + 0.000000000000000000000001m, + 0.0000000000000000000000001m, + 0.00000000000000000000000001m, + 0.000000000000000000000000001m, + 1.0m, + 1.00m, + 1.000m, + 1.0000m, + 1.00000m, + 1.000000m, + 1.0000000m, + 1.00000000m, + 1.000000000m, + 1.0000000000m, + 1.00000000000m, + 1.000000000000m, + 1.0000000000000m, + 1.00000000000000m, + 1.000000000000000m, + 1.0000000000000000m, + 1.00000000000000000m, + 1.000000000000000000m, + 1.0000000000000000000m, + 1.00000000000000000000m, + 1.000000000000000000000m, + 1.0000000000000000000000m, + 1.00000000000000000000000m, + 1.000000000000000000000000m, + 1.0000000000000000000000000m, + 1.00000000000000000000000000m, + 1.000000000000000000000000000m, + 10m, + 0.10m, + 0.010m, + 0.0010m, + 0.00010m, + 0.000010m, + 0.0000010m, + 0.00000010m, + 0.000000010m, + 0.0000000010m, + 0.00000000010m, + 0.000000000010m, + 0.0000000000010m, + 0.00000000000010m, + 0.000000000000010m, + 0.0000000000000010m, + 0.00000000000000010m, + 0.000000000000000010m, + 0.0000000000000000010m, + 0.00000000000000000010m, + 0.000000000000000000010m, + 0.0000000000000000000010m, + 0.00000000000000000000010m, + 0.000000000000000000000010m, + 0.0000000000000000000000010m, + 0.00000000000000000000000010m, + 10.0m, + 10.00m, + 10.000m, + 10.0000m, + 10.00000m, + 10.000000m, + 10.0000000m, + 10.00000000m, + 10.000000000m, + 10.0000000000m, + 10.00000000000m, + 10.000000000000m, + 10.0000000000000m, + 10.00000000000000m, + 10.000000000000000m, + 10.0000000000000000m, + 10.00000000000000000m, + 10.000000000000000000m, + 10.0000000000000000000m, + 10.00000000000000000000m, + 10.000000000000000000000m, + 10.0000000000000000000000m, + 10.00000000000000000000000m, + 10.000000000000000000000000m, + 10.0000000000000000000000000m, + 10.00000000000000000000000000m, + 100m, + 0.100m, + 0.0100m, + 0.00100m, + 0.000100m, + 0.0000100m, + 0.00000100m, + 0.000000100m, + 0.0000000100m, + 0.00000000100m, + 0.000000000100m, + 0.0000000000100m, + 0.00000000000100m, + 0.000000000000100m, + 0.0000000000000100m, + 0.00000000000000100m, + 0.000000000000000100m, + 0.0000000000000000100m, + 0.00000000000000000100m, + 0.000000000000000000100m, + 0.0000000000000000000100m, + 0.00000000000000000000100m, + 0.000000000000000000000100m, + 0.0000000000000000000000100m, + 100.0m, + 100.00m, + 100.000m, + 100.0000m, + 100.00000m, + 100.000000m, + 100.0000000m, + 100.00000000m, + 100.000000000m, + 100.0000000000m, + 100.00000000000m, + 100.000000000000m, + 100.0000000000000m, + 100.00000000000000m, + 100.000000000000000m, + 100.0000000000000000m, + 100.00000000000000000m, + 100.000000000000000000m, + 100.0000000000000000000m, + 100.00000000000000000000m, + 100.000000000000000000000m, + 100.0000000000000000000000m, + 100.00000000000000000000000m, + 100.000000000000000000000000m, + 100.0000000000000000000000000m, + 127m, + 12.7m, + 1.27m, + 0.127m, + 0.0127m, + 0.00127m, + 0.000127m, + 0.0000127m, + 0.00000127m, + 0.000000127m, + 0.0000000127m, + 0.00000000127m, + 0.000000000127m, + 0.0000000000127m, + 0.00000000000127m, + 0.000000000000127m, + 0.0000000000000127m, + 0.00000000000000127m, + 0.000000000000000127m, + 0.0000000000000000127m, + 0.00000000000000000127m, + 0.000000000000000000127m, + 0.0000000000000000000127m, + 0.00000000000000000000127m, + 0.000000000000000000000127m, + 0.0000000000000000000000127m, + 127.0m, + 127.00m, + 127.000m, + 127.0000m, + 127.00000m, + 127.000000m, + 127.0000000m, + 127.00000000m, + 127.000000000m, + 127.0000000000m, + 127.00000000000m, + 127.000000000000m, + 127.0000000000000m, + 127.00000000000000m, + 127.000000000000000m, + 127.0000000000000000m, + 127.00000000000000000m, + 127.000000000000000000m, + 127.0000000000000000000m, + 127.00000000000000000000m, + 127.000000000000000000000m, + 127.0000000000000000000000m, + 127.00000000000000000000000m, + 127.000000000000000000000000m, + 127.0000000000000000000000000m, + 255m, + 25.5m, + 2.55m, + 0.255m, + 0.0255m, + 0.00255m, + 0.000255m, + 0.0000255m, + 0.00000255m, + 0.000000255m, + 0.0000000255m, + 0.00000000255m, + 0.000000000255m, + 0.0000000000255m, + 0.00000000000255m, + 0.000000000000255m, + 0.0000000000000255m, + 0.00000000000000255m, + 0.000000000000000255m, + 0.0000000000000000255m, + 0.00000000000000000255m, + 0.000000000000000000255m, + 0.0000000000000000000255m, + 0.00000000000000000000255m, + 0.000000000000000000000255m, + 0.0000000000000000000000255m, + 255.0m, + 255.00m, + 255.000m, + 255.0000m, + 255.00000m, + 255.000000m, + 255.0000000m, + 255.00000000m, + 255.000000000m, + 255.0000000000m, + 255.00000000000m, + 255.000000000000m, + 255.0000000000000m, + 255.00000000000000m, + 255.000000000000000m, + 255.0000000000000000m, + 255.00000000000000000m, + 255.000000000000000000m, + 255.0000000000000000000m, + 255.00000000000000000000m, + 255.000000000000000000000m, + 255.0000000000000000000000m, + 255.00000000000000000000000m, + 255.000000000000000000000000m, + 255.0000000000000000000000000m, + 32767m, + 3276.7m, + 327.67m, + 32.767m, + 3.2767m, + 0.32767m, + 0.032767m, + 0.0032767m, + 0.00032767m, + 0.000032767m, + 0.0000032767m, + 0.00000032767m, + 0.000000032767m, + 0.0000000032767m, + 0.00000000032767m, + 0.000000000032767m, + 0.0000000000032767m, + 0.00000000000032767m, + 0.000000000000032767m, + 0.0000000000000032767m, + 0.00000000000000032767m, + 0.000000000000000032767m, + 0.0000000000000000032767m, + 0.00000000000000000032767m, + 32767.0m, + 32767.00m, + 32767.000m, + 32767.0000m, + 32767.00000m, + 32767.000000m, + 32767.0000000m, + 32767.00000000m, + 32767.000000000m, + 32767.0000000000m, + 32767.00000000000m, + 32767.000000000000m, + 32767.0000000000000m, + 32767.00000000000000m, + 32767.000000000000000m, + 32767.0000000000000000m, + 32767.00000000000000000m, + 32767.000000000000000000m, + 32767.0000000000000000000m, + 32767.00000000000000000000m, + 32767.000000000000000000000m, + 32767.0000000000000000000000m, + 32767.00000000000000000000000m, + 65535m, + 6553.5m, + 655.35m, + 65.535m, + 6.5535m, + 0.65535m, + 0.065535m, + 0.0065535m, + 0.00065535m, + 0.000065535m, + 0.0000065535m, + 0.00000065535m, + 0.000000065535m, + 0.0000000065535m, + 0.00000000065535m, + 0.000000000065535m, + 0.0000000000065535m, + 0.00000000000065535m, + 0.000000000000065535m, + 0.0000000000000065535m, + 0.00000000000000065535m, + 0.000000000000000065535m, + 0.0000000000000000065535m, + 0.00000000000000000065535m, + 65535.0m, + 65535.00m, + 65535.000m, + 65535.0000m, + 65535.00000m, + 65535.000000m, + 65535.0000000m, + 65535.00000000m, + 65535.000000000m, + 65535.0000000000m, + 65535.00000000000m, + 65535.000000000000m, + 65535.0000000000000m, + 65535.00000000000000m, + 65535.000000000000000m, + 65535.0000000000000000m, + 65535.00000000000000000m, + 65535.000000000000000000m, + 65535.0000000000000000000m, + 65535.00000000000000000000m, + 65535.000000000000000000000m, + 65535.0000000000000000000000m, + 65535.00000000000000000000000m, + 2147483647m, + 214748364.7m, + 21474836.47m, + 2147483.647m, + 214748.3647m, + 21474.83647m, + 2147.483647m, + 214.7483647m, + 21.47483647m, + 2.147483647m, + 0.2147483647m, + 0.02147483647m, + 0.002147483647m, + 0.0002147483647m, + 0.00002147483647m, + 0.000002147483647m, + 0.0000002147483647m, + 0.00000002147483647m, + 0.000000002147483647m, + 2147483647.0m, + 2147483647.00m, + 2147483647.000m, + 2147483647.0000m, + 2147483647.00000m, + 2147483647.000000m, + 2147483647.0000000m, + 2147483647.00000000m, + 2147483647.000000000m, + 2147483647.0000000000m, + 2147483647.00000000000m, + 2147483647.000000000000m, + 2147483647.0000000000000m, + 2147483647.00000000000000m, + 2147483647.000000000000000m, + 2147483647.0000000000000000m, + 2147483647.00000000000000000m, + 2147483647.000000000000000000m, + 4294967295m, + 429496729.5m, + 42949672.95m, + 4294967.295m, + 429496.7295m, + 42949.67295m, + 4294.967295m, + 429.4967295m, + 42.94967295m, + 4.294967295m, + 0.4294967295m, + 0.04294967295m, + 0.004294967295m, + 0.0004294967295m, + 0.00004294967295m, + 0.000004294967295m, + 0.0000004294967295m, + 0.00000004294967295m, + 0.000000004294967295m, + 4294967295.0m, + 4294967295.00m, + 4294967295.000m, + 4294967295.0000m, + 4294967295.00000m, + 4294967295.000000m, + 4294967295.0000000m, + 4294967295.00000000m, + 4294967295.000000000m, + 4294967295.0000000000m, + 4294967295.00000000000m, + 4294967295.000000000000m, + 4294967295.0000000000000m, + 4294967295.00000000000000m, + 4294967295.000000000000000m, + 4294967295.0000000000000000m, + 4294967295.00000000000000000m, + 4294967295.000000000000000000m, + 9223372036854775807m, + 922337203685477580.7m, + 92233720368547758.07m, + 9223372036854775.807m, + 922337203685477.5807m, + 92233720368547.75807m, + 9223372036854.775807m, + 922337203685.4775807m, + 92233720368.54775807m, + 9223372036.854775807m, + 9223372036854775807.0m, + 9223372036854775807.00m, + 9223372036854775807.000m, + 9223372036854775807.0000m, + 9223372036854775807.00000m, + 9223372036854775807.000000m, + 9223372036854775807.0000000m, + 9223372036854775807.00000000m, + 9223372036854775807.000000000m, + 18446744073709551615m, + 1844674407370955161.5m, + 184467440737095516.15m, + 18446744073709551.615m, + 1844674407370955.1615m, + 184467440737095.51615m, + 18446744073709.551615m, + 1844674407370.9551615m, + 184467440737.09551615m, + 18446744073709551615.0m, + 18446744073709551615.00m, + 18446744073709551615.000m, + 18446744073709551615.0000m, + 18446744073709551615.00000m, + 18446744073709551615.000000m, + 18446744073709551615.0000000m, + 18446744073709551615.00000000m, + -1m, + -0.1m, + -0.01m, + -0.001m, + -0.0001m, + -0.00001m, + -0.000001m, + -0.0000001m, + -0.00000001m, + -0.000000001m, + -0.0000000001m, + -0.00000000001m, + -0.000000000001m, + -0.0000000000001m, + -0.00000000000001m, + -0.000000000000001m, + -0.0000000000000001m, + -0.00000000000000001m, + -0.000000000000000001m, + -0.0000000000000000001m, + -0.00000000000000000001m, + -0.000000000000000000001m, + -0.0000000000000000000001m, + -0.00000000000000000000001m, + -0.000000000000000000000001m, + -0.0000000000000000000000001m, + -0.00000000000000000000000001m, + -0.000000000000000000000000001m, + -1.0m, + -1.00m, + -1.000m, + -1.0000m, + -1.00000m, + -1.000000m, + -1.0000000m, + -1.00000000m, + -1.000000000m, + -1.0000000000m, + -1.00000000000m, + -1.000000000000m, + -1.0000000000000m, + -1.00000000000000m, + -1.000000000000000m, + -1.0000000000000000m, + -1.00000000000000000m, + -1.000000000000000000m, + -1.0000000000000000000m, + -1.00000000000000000000m, + -1.000000000000000000000m, + -1.0000000000000000000000m, + -1.00000000000000000000000m, + -1.000000000000000000000000m, + -1.0000000000000000000000000m, + -1.00000000000000000000000000m, + -1.000000000000000000000000000m, + -10m, + -0.10m, + -0.010m, + -0.0010m, + -0.00010m, + -0.000010m, + -0.0000010m, + -0.00000010m, + -0.000000010m, + -0.0000000010m, + -0.00000000010m, + -0.000000000010m, + -0.0000000000010m, + -0.00000000000010m, + -0.000000000000010m, + -0.0000000000000010m, + -0.00000000000000010m, + -0.000000000000000010m, + -0.0000000000000000010m, + -0.00000000000000000010m, + -0.000000000000000000010m, + -0.0000000000000000000010m, + -0.00000000000000000000010m, + -0.000000000000000000000010m, + -0.0000000000000000000000010m, + -0.00000000000000000000000010m, + -10.0m, + -10.00m, + -10.000m, + -10.0000m, + -10.00000m, + -10.000000m, + -10.0000000m, + -10.00000000m, + -10.000000000m, + -10.0000000000m, + -10.00000000000m, + -10.000000000000m, + -10.0000000000000m, + -10.00000000000000m, + -10.000000000000000m, + -10.0000000000000000m, + -10.00000000000000000m, + -10.000000000000000000m, + -10.0000000000000000000m, + -10.00000000000000000000m, + -10.000000000000000000000m, + -10.0000000000000000000000m, + -10.00000000000000000000000m, + -10.000000000000000000000000m, + -10.0000000000000000000000000m, + -10.00000000000000000000000000m, + -100m, + -0.100m, + -0.0100m, + -0.00100m, + -0.000100m, + -0.0000100m, + -0.00000100m, + -0.000000100m, + -0.0000000100m, + -0.00000000100m, + -0.000000000100m, + -0.0000000000100m, + -0.00000000000100m, + -0.000000000000100m, + -0.0000000000000100m, + -0.00000000000000100m, + -0.000000000000000100m, + -0.0000000000000000100m, + -0.00000000000000000100m, + -0.000000000000000000100m, + -0.0000000000000000000100m, + -0.00000000000000000000100m, + -0.000000000000000000000100m, + -0.0000000000000000000000100m, + -100.0m, + -100.00m, + -100.000m, + -100.0000m, + -100.00000m, + -100.000000m, + -100.0000000m, + -100.00000000m, + -100.000000000m, + -100.0000000000m, + -100.00000000000m, + -100.000000000000m, + -100.0000000000000m, + -100.00000000000000m, + -100.000000000000000m, + -100.0000000000000000m, + -100.00000000000000000m, + -100.000000000000000000m, + -100.0000000000000000000m, + -100.00000000000000000000m, + -100.000000000000000000000m, + -100.0000000000000000000000m, + -100.00000000000000000000000m, + -100.000000000000000000000000m, + -100.0000000000000000000000000m, + -128m, + -12.8m, + -1.28m, + -0.128m, + -0.0128m, + -0.00128m, + -0.000128m, + -0.0000128m, + -0.00000128m, + -0.000000128m, + -0.0000000128m, + -0.00000000128m, + -0.000000000128m, + -0.0000000000128m, + -0.00000000000128m, + -0.000000000000128m, + -0.0000000000000128m, + -0.00000000000000128m, + -0.000000000000000128m, + -0.0000000000000000128m, + -0.00000000000000000128m, + -0.000000000000000000128m, + -0.0000000000000000000128m, + -0.00000000000000000000128m, + -0.000000000000000000000128m, + -0.0000000000000000000000128m, + -128.0m, + -128.00m, + -128.000m, + -128.0000m, + -128.00000m, + -128.000000m, + -128.0000000m, + -128.00000000m, + -128.000000000m, + -128.0000000000m, + -128.00000000000m, + -128.000000000000m, + -128.0000000000000m, + -128.00000000000000m, + -128.000000000000000m, + -128.0000000000000000m, + -128.00000000000000000m, + -128.000000000000000000m, + -128.0000000000000000000m, + -128.00000000000000000000m, + -128.000000000000000000000m, + -128.0000000000000000000000m, + -128.00000000000000000000000m, + -128.000000000000000000000000m, + -128.0000000000000000000000000m, + -32768m, + -3276.8m, + -327.68m, + -32.768m, + -3.2768m, + -0.32768m, + -0.032768m, + -0.0032768m, + -0.00032768m, + -0.000032768m, + -0.0000032768m, + -0.00000032768m, + -0.000000032768m, + -0.0000000032768m, + -0.00000000032768m, + -0.000000000032768m, + -0.0000000000032768m, + -0.00000000000032768m, + -0.000000000000032768m, + -0.0000000000000032768m, + -0.00000000000000032768m, + -0.000000000000000032768m, + -0.0000000000000000032768m, + -0.00000000000000000032768m, + -32768.0m, + -32768.00m, + -32768.000m, + -32768.0000m, + -32768.00000m, + -32768.000000m, + -32768.0000000m, + -32768.00000000m, + -32768.000000000m, + -32768.0000000000m, + -32768.00000000000m, + -32768.000000000000m, + -32768.0000000000000m, + -32768.00000000000000m, + -32768.000000000000000m, + -32768.0000000000000000m, + -32768.00000000000000000m, + -32768.000000000000000000m, + -32768.0000000000000000000m, + -32768.00000000000000000000m, + -32768.000000000000000000000m, + -32768.0000000000000000000000m, + -32768.00000000000000000000000m, + -2147483648m, + -214748364.8m, + -21474836.48m, + -2147483.648m, + -214748.3648m, + -21474.83648m, + -2147.483648m, + -214.7483648m, + -21.47483648m, + -2.147483648m, + -0.2147483648m, + -0.02147483648m, + -0.002147483648m, + -0.0002147483648m, + -0.00002147483648m, + -0.000002147483648m, + -0.0000002147483648m, + -0.00000002147483648m, + -0.000000002147483648m, + -2147483648.0m, + -2147483648.00m, + -2147483648.000m, + -2147483648.0000m, + -2147483648.00000m, + -2147483648.000000m, + -2147483648.0000000m, + -2147483648.00000000m, + -2147483648.000000000m, + -2147483648.0000000000m, + -2147483648.00000000000m, + -2147483648.000000000000m, + -2147483648.0000000000000m, + -2147483648.00000000000000m, + -2147483648.000000000000000m, + -2147483648.0000000000000000m, + -2147483648.00000000000000000m, + -2147483648.000000000000000000m, + -9223372036854775808m, + -922337203685477580.8m, + -92233720368547758.08m, + -9223372036854775.808m, + -922337203685477.5808m, + -92233720368547.75808m, + -9223372036854.775808m, + -922337203685.4775808m, + -92233720368.54775808m, + -9223372036.854775808m, + -9223372036854775808.0m, + -9223372036854775808.00m, + -9223372036854775808.000m, + -9223372036854775808.0000m, + -9223372036854775808.00000m, + -9223372036854775808.000000m, + -9223372036854775808.0000000m, + -9223372036854775808.00000000m, + -9223372036854775808.000000000m, + ]; + + public ScaleMode[] ScaleModes => Enum.GetValues(); + public MidpointRounding[] RoundingModes => Enum.GetValues(); + + public CultureInfo[] Cultures => + [ + CultureInfo.InvariantCulture, + CultureInfo.GetCultureInfo("ar-001"), // Arabic (world) + CultureInfo.GetCultureInfo("ar-AE"), // Arabic (United Arab Emirates) + CultureInfo.GetCultureInfo("en-001"), // English (world) + CultureInfo.GetCultureInfo("en-150"), // English (Europe) + CultureInfo.GetCultureInfo("en-GB"), // English (United Kingdom) + CultureInfo.GetCultureInfo("en-US"), // English (United States) + CultureInfo.GetCultureInfo("en-US-POSIX"), // English (United States, Computer) + CultureInfo.GetCultureInfo("en-CV"), // English (Cape Verde) + CultureInfo.GetCultureInfo("kea-CV"), // Kabuverdianu (Cape Verde) + CultureInfo.GetCultureInfo("pt-CV"), // Portuguese (Cape Verde) + CultureInfo.GetCultureInfo("eu"), // Basque + CultureInfo.GetCultureInfo("eu-ES"), // Basque (Spain) + CultureInfo.GetCultureInfo("bg-BG"), // Bulgarian (Bulgaria) + CultureInfo.GetCultureInfo("de-DE"), // German (Germany) + CultureInfo.GetCultureInfo("es-ES"), // Spanish (Spain) + CultureInfo.GetCultureInfo("fi-FI"), // Finnish (Finland) + CultureInfo.GetCultureInfo("fo-FO"), // Faroese (Faroe Islands) + CultureInfo.GetCultureInfo("fr-FR"), // French (France) + CultureInfo.GetCultureInfo("hr-HR"), // Croatian (Croatia) + CultureInfo.GetCultureInfo("hu-HU"), // Hungarian (Hungary) + CultureInfo.GetCultureInfo("id-ID"), // Indonesian (Indonesia) + CultureInfo.GetCultureInfo("is-IS"), // Icelandic (Iceland) + CultureInfo.GetCultureInfo("it-IT"), // Italian (Italy) + CultureInfo.GetCultureInfo("lt-LT"), // Lithuanian (Lithuania) + CultureInfo.GetCultureInfo("lv-LV"), // Latvian (Latvia) + CultureInfo.GetCultureInfo("mg-MG"), // Malagasy (Madagascar) + CultureInfo.GetCultureInfo("mk-MK"), // Macedonian (North Macedonia) + CultureInfo.GetCultureInfo("mn-MN"), // Mongolian (Mongolia) + CultureInfo.GetCultureInfo("mt-MT"), // Maltese (Malta) + CultureInfo.GetCultureInfo("nl-NL"), // Dutch (Netherlands) + CultureInfo.GetCultureInfo("pl-PL"), // Polish (Poland) + CultureInfo.GetCultureInfo("pt-PT"), // Portuguese (Portugal) + CultureInfo.GetCultureInfo("ro-RO"), // Romanian (Romania) + CultureInfo.GetCultureInfo("ru-RU"), // Russian (Russia) + CultureInfo.GetCultureInfo("rw-RW"), // Kinyarwanda (Rwanda) + CultureInfo.GetCultureInfo("se-SE"), // Northern Sami (Sweden) + CultureInfo.GetCultureInfo("sk-SK"), // Slovak (Slovakia) + CultureInfo.GetCultureInfo("so-SO"), // Somali (Somalia) + CultureInfo.GetCultureInfo("th-TH"), // Thai (Thailand) + CultureInfo.GetCultureInfo("to-TO"), // Tongan (Tonga) + CultureInfo.GetCultureInfo("tr-TR"), // Turkish (Türkiye) + CultureInfo.GetCultureInfo("zh-Hans-CN"), // Chinese, Simplified (China mainland) + CultureInfo.GetCultureInfo("zh-Hant-CN"), // Chinese, Traditional (China mainland) + ]; +} diff --git a/OnixLabs.Numerics.UnitTests.Data/NumericDataProviders/INumericDataProvider.cs b/OnixLabs.Numerics.UnitTests.Data/NumericDataProviders/INumericDataProvider.cs new file mode 100644 index 0000000..a4ca340 --- /dev/null +++ b/OnixLabs.Numerics.UnitTests.Data/NumericDataProviders/INumericDataProvider.cs @@ -0,0 +1,37 @@ +// 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.Globalization; + +namespace OnixLabs.Numerics.UnitTests.Data.NumericDataProviders; + +internal interface INumericDataProvider +{ + int MinScale { get; } + int MaxScale { get; } + int RandomCount { get; } + int[] IntegerValues { get; } + int[] IntegerScales { get; } + decimal[] DecimalValues { get; } + ScaleMode[] ScaleModes { get; } + MidpointRounding[] RoundingModes { get; } + CultureInfo[] Cultures { get; } + + public static INumericDataProvider GetProvider() + { + string useFullNumericProvider = Environment.GetEnvironmentVariable("USE_FULL_NUMERIC_PROVIDER") ?? bool.FalseString; + if (useFullNumericProvider.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase)) return new FullNumericDataProvider(); + return new SlimNumericDataProvider(); + } +} diff --git a/OnixLabs.Numerics.UnitTests.Data/NumericDataProviders/SlimNumericDataProvider.cs b/OnixLabs.Numerics.UnitTests.Data/NumericDataProviders/SlimNumericDataProvider.cs new file mode 100644 index 0000000..63a3cef --- /dev/null +++ b/OnixLabs.Numerics.UnitTests.Data/NumericDataProviders/SlimNumericDataProvider.cs @@ -0,0 +1,79 @@ +// 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.Globalization; + +namespace OnixLabs.Numerics.UnitTests.Data.NumericDataProviders; + +internal sealed class SlimNumericDataProvider : INumericDataProvider +{ + public int MinScale => 0; + public int MaxScale => 3; + public int RandomCount => 5; + + public int[] IntegerValues => [0, +1, +3, +1_234_567_890, -1, -3, -1_234_567_890]; + public int[] IntegerScales => [0, 3]; + + public decimal[] DecimalValues => + [ + 0m, + 1m, + 0.000000000000000000000000001m, + 127m, + 0.0000000000000000000000127m, + 255m, + 0.0000000000000000000000255m, + 32767m, + 0.00000000000000000032767m, + 65535m, + 0.00000000000000000065535m, + 2147483647m, + 0.000000002147483647m, + 4294967295m, + 0.000000004294967295m, + 9223372036854775807m, + 18446744073709551615m, + 18446744073709551.615m, + + -1m, + -0.000000000000000000000000001m, + -127m, + -0.0000000000000000000000127m, + -255m, + -0.0000000000000000000000255m, + -32767m, + -0.00000000000000000032767m, + -65535m, + -0.00000000000000000065535m, + -2147483647m, + -0.000000002147483647m, + -4294967295m, + -0.000000004294967295m, + -9223372036854775807m, + -18446744073709551615m, + -18446744073709551.615m, + ]; + + public ScaleMode[] ScaleModes => Enum.GetValues(); + public MidpointRounding[] RoundingModes => Enum.GetValues(); + + public CultureInfo[] Cultures => + [ + CultureInfo.InvariantCulture, + CultureInfo.GetCultureInfo("ar-001"), // Arabic (world) + CultureInfo.GetCultureInfo("en-001"), // English (world) + CultureInfo.GetCultureInfo("eu"), // Basque + CultureInfo.GetCultureInfo("zh-Hant-CN"), // Chinese, Traditional (China mainland) + ]; +} diff --git a/OnixLabs.Numerics.UnitTests.Data/TestDataGenerator.cs b/OnixLabs.Numerics.UnitTests.Data/TestDataGenerator.cs index d15dcc2..733bb04 100644 --- a/OnixLabs.Numerics.UnitTests.Data/TestDataGenerator.cs +++ b/OnixLabs.Numerics.UnitTests.Data/TestDataGenerator.cs @@ -13,896 +13,28 @@ // limitations under the License. using System.Globalization; +using OnixLabs.Numerics.UnitTests.Data.NumericDataProviders; namespace OnixLabs.Numerics.UnitTests.Data; internal static class TestDataGenerator { - private const int MinScale = 0; - private const int MaxScale = 28; - - private static readonly int[] DefaultValues = - [ - 0, - - +1, +2, +3, +4, +5, +6, +7, +8, +9, - +10, +100, +1_000, +10_000, +1_000_000_000, - +12, +123, +1_234, +12_345, +1_234_567_890, - - -1, -2, -3, -4, -5, -6, -7, -8, -9, - -10, -100, -1_000, -10_000, -1_000_000_000, - -12, -123, -1_234, -12_345, -1_234_567_890 - ]; - - private static readonly int[] DefaultScales = [0, 1, 2, 3, 4, 5, 10]; - - private static readonly ScaleMode[] DefaultModes = Enum.GetValues(); - - private static readonly decimal[] StaticValues = - [ - 0m, - 0.0m, - 0.00m, - 0.000m, - 0.0000m, - 0.00000m, - 0.000000m, - 0.0000000m, - 0.00000000m, - 0.000000000m, - 0.0000000000m, - 0.00000000000m, - 0.000000000000m, - 0.0000000000000m, - 0.00000000000000m, - 0.000000000000000m, - 0.0000000000000000m, - 0.00000000000000000m, - 0.000000000000000000m, - 0.0000000000000000000m, - 0.00000000000000000000m, - 0.000000000000000000000m, - 0.0000000000000000000000m, - 0.00000000000000000000000m, - 0.000000000000000000000000m, - 0.0000000000000000000000000m, - 0.00000000000000000000000000m, - 0.000000000000000000000000000m, - 1m, - 0.1m, - 0.01m, - 0.001m, - 0.0001m, - 0.00001m, - 0.000001m, - 0.0000001m, - 0.00000001m, - 0.000000001m, - 0.0000000001m, - 0.00000000001m, - 0.000000000001m, - 0.0000000000001m, - 0.00000000000001m, - 0.000000000000001m, - 0.0000000000000001m, - 0.00000000000000001m, - 0.000000000000000001m, - 0.0000000000000000001m, - 0.00000000000000000001m, - 0.000000000000000000001m, - 0.0000000000000000000001m, - 0.00000000000000000000001m, - 0.000000000000000000000001m, - 0.0000000000000000000000001m, - 0.00000000000000000000000001m, - 0.000000000000000000000000001m, - 1.0m, - 1.00m, - 1.000m, - 1.0000m, - 1.00000m, - 1.000000m, - 1.0000000m, - 1.00000000m, - 1.000000000m, - 1.0000000000m, - 1.00000000000m, - 1.000000000000m, - 1.0000000000000m, - 1.00000000000000m, - 1.000000000000000m, - 1.0000000000000000m, - 1.00000000000000000m, - 1.000000000000000000m, - 1.0000000000000000000m, - 1.00000000000000000000m, - 1.000000000000000000000m, - 1.0000000000000000000000m, - 1.00000000000000000000000m, - 1.000000000000000000000000m, - 1.0000000000000000000000000m, - 1.00000000000000000000000000m, - 1.000000000000000000000000000m, - 10m, - 0.10m, - 0.010m, - 0.0010m, - 0.00010m, - 0.000010m, - 0.0000010m, - 0.00000010m, - 0.000000010m, - 0.0000000010m, - 0.00000000010m, - 0.000000000010m, - 0.0000000000010m, - 0.00000000000010m, - 0.000000000000010m, - 0.0000000000000010m, - 0.00000000000000010m, - 0.000000000000000010m, - 0.0000000000000000010m, - 0.00000000000000000010m, - 0.000000000000000000010m, - 0.0000000000000000000010m, - 0.00000000000000000000010m, - 0.000000000000000000000010m, - 0.0000000000000000000000010m, - 0.00000000000000000000000010m, - 10.0m, - 10.00m, - 10.000m, - 10.0000m, - 10.00000m, - 10.000000m, - 10.0000000m, - 10.00000000m, - 10.000000000m, - 10.0000000000m, - 10.00000000000m, - 10.000000000000m, - 10.0000000000000m, - 10.00000000000000m, - 10.000000000000000m, - 10.0000000000000000m, - 10.00000000000000000m, - 10.000000000000000000m, - 10.0000000000000000000m, - 10.00000000000000000000m, - 10.000000000000000000000m, - 10.0000000000000000000000m, - 10.00000000000000000000000m, - 10.000000000000000000000000m, - 10.0000000000000000000000000m, - 10.00000000000000000000000000m, - 100m, - 0.100m, - 0.0100m, - 0.00100m, - 0.000100m, - 0.0000100m, - 0.00000100m, - 0.000000100m, - 0.0000000100m, - 0.00000000100m, - 0.000000000100m, - 0.0000000000100m, - 0.00000000000100m, - 0.000000000000100m, - 0.0000000000000100m, - 0.00000000000000100m, - 0.000000000000000100m, - 0.0000000000000000100m, - 0.00000000000000000100m, - 0.000000000000000000100m, - 0.0000000000000000000100m, - 0.00000000000000000000100m, - 0.000000000000000000000100m, - 0.0000000000000000000000100m, - 100.0m, - 100.00m, - 100.000m, - 100.0000m, - 100.00000m, - 100.000000m, - 100.0000000m, - 100.00000000m, - 100.000000000m, - 100.0000000000m, - 100.00000000000m, - 100.000000000000m, - 100.0000000000000m, - 100.00000000000000m, - 100.000000000000000m, - 100.0000000000000000m, - 100.00000000000000000m, - 100.000000000000000000m, - 100.0000000000000000000m, - 100.00000000000000000000m, - 100.000000000000000000000m, - 100.0000000000000000000000m, - 100.00000000000000000000000m, - 100.000000000000000000000000m, - 100.0000000000000000000000000m, - 127m, - 12.7m, - 1.27m, - 0.127m, - 0.0127m, - 0.00127m, - 0.000127m, - 0.0000127m, - 0.00000127m, - 0.000000127m, - 0.0000000127m, - 0.00000000127m, - 0.000000000127m, - 0.0000000000127m, - 0.00000000000127m, - 0.000000000000127m, - 0.0000000000000127m, - 0.00000000000000127m, - 0.000000000000000127m, - 0.0000000000000000127m, - 0.00000000000000000127m, - 0.000000000000000000127m, - 0.0000000000000000000127m, - 0.00000000000000000000127m, - 0.000000000000000000000127m, - 0.0000000000000000000000127m, - 127.0m, - 127.00m, - 127.000m, - 127.0000m, - 127.00000m, - 127.000000m, - 127.0000000m, - 127.00000000m, - 127.000000000m, - 127.0000000000m, - 127.00000000000m, - 127.000000000000m, - 127.0000000000000m, - 127.00000000000000m, - 127.000000000000000m, - 127.0000000000000000m, - 127.00000000000000000m, - 127.000000000000000000m, - 127.0000000000000000000m, - 127.00000000000000000000m, - 127.000000000000000000000m, - 127.0000000000000000000000m, - 127.00000000000000000000000m, - 127.000000000000000000000000m, - 127.0000000000000000000000000m, - 255m, - 25.5m, - 2.55m, - 0.255m, - 0.0255m, - 0.00255m, - 0.000255m, - 0.0000255m, - 0.00000255m, - 0.000000255m, - 0.0000000255m, - 0.00000000255m, - 0.000000000255m, - 0.0000000000255m, - 0.00000000000255m, - 0.000000000000255m, - 0.0000000000000255m, - 0.00000000000000255m, - 0.000000000000000255m, - 0.0000000000000000255m, - 0.00000000000000000255m, - 0.000000000000000000255m, - 0.0000000000000000000255m, - 0.00000000000000000000255m, - 0.000000000000000000000255m, - 0.0000000000000000000000255m, - 255.0m, - 255.00m, - 255.000m, - 255.0000m, - 255.00000m, - 255.000000m, - 255.0000000m, - 255.00000000m, - 255.000000000m, - 255.0000000000m, - 255.00000000000m, - 255.000000000000m, - 255.0000000000000m, - 255.00000000000000m, - 255.000000000000000m, - 255.0000000000000000m, - 255.00000000000000000m, - 255.000000000000000000m, - 255.0000000000000000000m, - 255.00000000000000000000m, - 255.000000000000000000000m, - 255.0000000000000000000000m, - 255.00000000000000000000000m, - 255.000000000000000000000000m, - 255.0000000000000000000000000m, - 32767m, - 3276.7m, - 327.67m, - 32.767m, - 3.2767m, - 0.32767m, - 0.032767m, - 0.0032767m, - 0.00032767m, - 0.000032767m, - 0.0000032767m, - 0.00000032767m, - 0.000000032767m, - 0.0000000032767m, - 0.00000000032767m, - 0.000000000032767m, - 0.0000000000032767m, - 0.00000000000032767m, - 0.000000000000032767m, - 0.0000000000000032767m, - 0.00000000000000032767m, - 0.000000000000000032767m, - 0.0000000000000000032767m, - 0.00000000000000000032767m, - 32767.0m, - 32767.00m, - 32767.000m, - 32767.0000m, - 32767.00000m, - 32767.000000m, - 32767.0000000m, - 32767.00000000m, - 32767.000000000m, - 32767.0000000000m, - 32767.00000000000m, - 32767.000000000000m, - 32767.0000000000000m, - 32767.00000000000000m, - 32767.000000000000000m, - 32767.0000000000000000m, - 32767.00000000000000000m, - 32767.000000000000000000m, - 32767.0000000000000000000m, - 32767.00000000000000000000m, - 32767.000000000000000000000m, - 32767.0000000000000000000000m, - 32767.00000000000000000000000m, - 65535m, - 6553.5m, - 655.35m, - 65.535m, - 6.5535m, - 0.65535m, - 0.065535m, - 0.0065535m, - 0.00065535m, - 0.000065535m, - 0.0000065535m, - 0.00000065535m, - 0.000000065535m, - 0.0000000065535m, - 0.00000000065535m, - 0.000000000065535m, - 0.0000000000065535m, - 0.00000000000065535m, - 0.000000000000065535m, - 0.0000000000000065535m, - 0.00000000000000065535m, - 0.000000000000000065535m, - 0.0000000000000000065535m, - 0.00000000000000000065535m, - 65535.0m, - 65535.00m, - 65535.000m, - 65535.0000m, - 65535.00000m, - 65535.000000m, - 65535.0000000m, - 65535.00000000m, - 65535.000000000m, - 65535.0000000000m, - 65535.00000000000m, - 65535.000000000000m, - 65535.0000000000000m, - 65535.00000000000000m, - 65535.000000000000000m, - 65535.0000000000000000m, - 65535.00000000000000000m, - 65535.000000000000000000m, - 65535.0000000000000000000m, - 65535.00000000000000000000m, - 65535.000000000000000000000m, - 65535.0000000000000000000000m, - 65535.00000000000000000000000m, - 2147483647m, - 214748364.7m, - 21474836.47m, - 2147483.647m, - 214748.3647m, - 21474.83647m, - 2147.483647m, - 214.7483647m, - 21.47483647m, - 2.147483647m, - 0.2147483647m, - 0.02147483647m, - 0.002147483647m, - 0.0002147483647m, - 0.00002147483647m, - 0.000002147483647m, - 0.0000002147483647m, - 0.00000002147483647m, - 0.000000002147483647m, - 2147483647.0m, - 2147483647.00m, - 2147483647.000m, - 2147483647.0000m, - 2147483647.00000m, - 2147483647.000000m, - 2147483647.0000000m, - 2147483647.00000000m, - 2147483647.000000000m, - 2147483647.0000000000m, - 2147483647.00000000000m, - 2147483647.000000000000m, - 2147483647.0000000000000m, - 2147483647.00000000000000m, - 2147483647.000000000000000m, - 2147483647.0000000000000000m, - 2147483647.00000000000000000m, - 2147483647.000000000000000000m, - 4294967295m, - 429496729.5m, - 42949672.95m, - 4294967.295m, - 429496.7295m, - 42949.67295m, - 4294.967295m, - 429.4967295m, - 42.94967295m, - 4.294967295m, - 0.4294967295m, - 0.04294967295m, - 0.004294967295m, - 0.0004294967295m, - 0.00004294967295m, - 0.000004294967295m, - 0.0000004294967295m, - 0.00000004294967295m, - 0.000000004294967295m, - 4294967295.0m, - 4294967295.00m, - 4294967295.000m, - 4294967295.0000m, - 4294967295.00000m, - 4294967295.000000m, - 4294967295.0000000m, - 4294967295.00000000m, - 4294967295.000000000m, - 4294967295.0000000000m, - 4294967295.00000000000m, - 4294967295.000000000000m, - 4294967295.0000000000000m, - 4294967295.00000000000000m, - 4294967295.000000000000000m, - 4294967295.0000000000000000m, - 4294967295.00000000000000000m, - 4294967295.000000000000000000m, - 9223372036854775807m, - 922337203685477580.7m, - 92233720368547758.07m, - 9223372036854775.807m, - 922337203685477.5807m, - 92233720368547.75807m, - 9223372036854.775807m, - 922337203685.4775807m, - 92233720368.54775807m, - 9223372036.854775807m, - 9223372036854775807.0m, - 9223372036854775807.00m, - 9223372036854775807.000m, - 9223372036854775807.0000m, - 9223372036854775807.00000m, - 9223372036854775807.000000m, - 9223372036854775807.0000000m, - 9223372036854775807.00000000m, - 9223372036854775807.000000000m, - 18446744073709551615m, - 1844674407370955161.5m, - 184467440737095516.15m, - 18446744073709551.615m, - 1844674407370955.1615m, - 184467440737095.51615m, - 18446744073709.551615m, - 1844674407370.9551615m, - 184467440737.09551615m, - 18446744073709551615.0m, - 18446744073709551615.00m, - 18446744073709551615.000m, - 18446744073709551615.0000m, - 18446744073709551615.00000m, - 18446744073709551615.000000m, - 18446744073709551615.0000000m, - 18446744073709551615.00000000m, - -1m, - -0.1m, - -0.01m, - -0.001m, - -0.0001m, - -0.00001m, - -0.000001m, - -0.0000001m, - -0.00000001m, - -0.000000001m, - -0.0000000001m, - -0.00000000001m, - -0.000000000001m, - -0.0000000000001m, - -0.00000000000001m, - -0.000000000000001m, - -0.0000000000000001m, - -0.00000000000000001m, - -0.000000000000000001m, - -0.0000000000000000001m, - -0.00000000000000000001m, - -0.000000000000000000001m, - -0.0000000000000000000001m, - -0.00000000000000000000001m, - -0.000000000000000000000001m, - -0.0000000000000000000000001m, - -0.00000000000000000000000001m, - -0.000000000000000000000000001m, - -1.0m, - -1.00m, - -1.000m, - -1.0000m, - -1.00000m, - -1.000000m, - -1.0000000m, - -1.00000000m, - -1.000000000m, - -1.0000000000m, - -1.00000000000m, - -1.000000000000m, - -1.0000000000000m, - -1.00000000000000m, - -1.000000000000000m, - -1.0000000000000000m, - -1.00000000000000000m, - -1.000000000000000000m, - -1.0000000000000000000m, - -1.00000000000000000000m, - -1.000000000000000000000m, - -1.0000000000000000000000m, - -1.00000000000000000000000m, - -1.000000000000000000000000m, - -1.0000000000000000000000000m, - -1.00000000000000000000000000m, - -1.000000000000000000000000000m, - -10m, - -0.10m, - -0.010m, - -0.0010m, - -0.00010m, - -0.000010m, - -0.0000010m, - -0.00000010m, - -0.000000010m, - -0.0000000010m, - -0.00000000010m, - -0.000000000010m, - -0.0000000000010m, - -0.00000000000010m, - -0.000000000000010m, - -0.0000000000000010m, - -0.00000000000000010m, - -0.000000000000000010m, - -0.0000000000000000010m, - -0.00000000000000000010m, - -0.000000000000000000010m, - -0.0000000000000000000010m, - -0.00000000000000000000010m, - -0.000000000000000000000010m, - -0.0000000000000000000000010m, - -0.00000000000000000000000010m, - -10.0m, - -10.00m, - -10.000m, - -10.0000m, - -10.00000m, - -10.000000m, - -10.0000000m, - -10.00000000m, - -10.000000000m, - -10.0000000000m, - -10.00000000000m, - -10.000000000000m, - -10.0000000000000m, - -10.00000000000000m, - -10.000000000000000m, - -10.0000000000000000m, - -10.00000000000000000m, - -10.000000000000000000m, - -10.0000000000000000000m, - -10.00000000000000000000m, - -10.000000000000000000000m, - -10.0000000000000000000000m, - -10.00000000000000000000000m, - -10.000000000000000000000000m, - -10.0000000000000000000000000m, - -10.00000000000000000000000000m, - -100m, - -0.100m, - -0.0100m, - -0.00100m, - -0.000100m, - -0.0000100m, - -0.00000100m, - -0.000000100m, - -0.0000000100m, - -0.00000000100m, - -0.000000000100m, - -0.0000000000100m, - -0.00000000000100m, - -0.000000000000100m, - -0.0000000000000100m, - -0.00000000000000100m, - -0.000000000000000100m, - -0.0000000000000000100m, - -0.00000000000000000100m, - -0.000000000000000000100m, - -0.0000000000000000000100m, - -0.00000000000000000000100m, - -0.000000000000000000000100m, - -0.0000000000000000000000100m, - -100.0m, - -100.00m, - -100.000m, - -100.0000m, - -100.00000m, - -100.000000m, - -100.0000000m, - -100.00000000m, - -100.000000000m, - -100.0000000000m, - -100.00000000000m, - -100.000000000000m, - -100.0000000000000m, - -100.00000000000000m, - -100.000000000000000m, - -100.0000000000000000m, - -100.00000000000000000m, - -100.000000000000000000m, - -100.0000000000000000000m, - -100.00000000000000000000m, - -100.000000000000000000000m, - -100.0000000000000000000000m, - -100.00000000000000000000000m, - -100.000000000000000000000000m, - -100.0000000000000000000000000m, - -128m, - -12.8m, - -1.28m, - -0.128m, - -0.0128m, - -0.00128m, - -0.000128m, - -0.0000128m, - -0.00000128m, - -0.000000128m, - -0.0000000128m, - -0.00000000128m, - -0.000000000128m, - -0.0000000000128m, - -0.00000000000128m, - -0.000000000000128m, - -0.0000000000000128m, - -0.00000000000000128m, - -0.000000000000000128m, - -0.0000000000000000128m, - -0.00000000000000000128m, - -0.000000000000000000128m, - -0.0000000000000000000128m, - -0.00000000000000000000128m, - -0.000000000000000000000128m, - -0.0000000000000000000000128m, - -128.0m, - -128.00m, - -128.000m, - -128.0000m, - -128.00000m, - -128.000000m, - -128.0000000m, - -128.00000000m, - -128.000000000m, - -128.0000000000m, - -128.00000000000m, - -128.000000000000m, - -128.0000000000000m, - -128.00000000000000m, - -128.000000000000000m, - -128.0000000000000000m, - -128.00000000000000000m, - -128.000000000000000000m, - -128.0000000000000000000m, - -128.00000000000000000000m, - -128.000000000000000000000m, - -128.0000000000000000000000m, - -128.00000000000000000000000m, - -128.000000000000000000000000m, - -128.0000000000000000000000000m, - -32768m, - -3276.8m, - -327.68m, - -32.768m, - -3.2768m, - -0.32768m, - -0.032768m, - -0.0032768m, - -0.00032768m, - -0.000032768m, - -0.0000032768m, - -0.00000032768m, - -0.000000032768m, - -0.0000000032768m, - -0.00000000032768m, - -0.000000000032768m, - -0.0000000000032768m, - -0.00000000000032768m, - -0.000000000000032768m, - -0.0000000000000032768m, - -0.00000000000000032768m, - -0.000000000000000032768m, - -0.0000000000000000032768m, - -0.00000000000000000032768m, - -32768.0m, - -32768.00m, - -32768.000m, - -32768.0000m, - -32768.00000m, - -32768.000000m, - -32768.0000000m, - -32768.00000000m, - -32768.000000000m, - -32768.0000000000m, - -32768.00000000000m, - -32768.000000000000m, - -32768.0000000000000m, - -32768.00000000000000m, - -32768.000000000000000m, - -32768.0000000000000000m, - -32768.00000000000000000m, - -32768.000000000000000000m, - -32768.0000000000000000000m, - -32768.00000000000000000000m, - -32768.000000000000000000000m, - -32768.0000000000000000000000m, - -32768.00000000000000000000000m, - -2147483648m, - -214748364.8m, - -21474836.48m, - -2147483.648m, - -214748.3648m, - -21474.83648m, - -2147.483648m, - -214.7483648m, - -21.47483648m, - -2.147483648m, - -0.2147483648m, - -0.02147483648m, - -0.002147483648m, - -0.0002147483648m, - -0.00002147483648m, - -0.000002147483648m, - -0.0000002147483648m, - -0.00000002147483648m, - -0.000000002147483648m, - -2147483648.0m, - -2147483648.00m, - -2147483648.000m, - -2147483648.0000m, - -2147483648.00000m, - -2147483648.000000m, - -2147483648.0000000m, - -2147483648.00000000m, - -2147483648.000000000m, - -2147483648.0000000000m, - -2147483648.00000000000m, - -2147483648.000000000000m, - -2147483648.0000000000000m, - -2147483648.00000000000000m, - -2147483648.000000000000000m, - -2147483648.0000000000000000m, - -2147483648.00000000000000000m, - -2147483648.000000000000000000m, - -9223372036854775808m, - -922337203685477580.8m, - -92233720368547758.08m, - -9223372036854775.808m, - -922337203685477.5808m, - -92233720368547.75808m, - -9223372036854.775808m, - -922337203685.4775808m, - -92233720368.54775808m, - -9223372036.854775808m, - -9223372036854775808.0m, - -9223372036854775808.00m, - -9223372036854775808.000m, - -9223372036854775808.0000m, - -9223372036854775808.00000m, - -9223372036854775808.000000m, - -9223372036854775808.0000000m, - -9223372036854775808.00000000m, - -9223372036854775808.000000000m, - ]; - - private static readonly CultureInfo[] Cultures = - [ - CultureInfo.InvariantCulture, - CultureInfo.GetCultureInfo("ar-001"), // Arabic (world) - CultureInfo.GetCultureInfo("ar-AE"), // Arabic (United Arab Emirates) - CultureInfo.GetCultureInfo("en-001"), // English (world) - CultureInfo.GetCultureInfo("en-150"), // English (Europe) - CultureInfo.GetCultureInfo("en-GB"), // English (United Kingdom) - CultureInfo.GetCultureInfo("en-US"), // English (United States) - CultureInfo.GetCultureInfo("en-US-POSIX"), // English (United States, Computer) - CultureInfo.GetCultureInfo("en-CV"), // English (Cape Verde) - CultureInfo.GetCultureInfo("kea-CV"), // Kabuverdianu (Cape Verde) - CultureInfo.GetCultureInfo("pt-CV"), // Portuguese (Cape Verde) - CultureInfo.GetCultureInfo("eu"), // Basque - CultureInfo.GetCultureInfo("eu-ES"), // Basque (Spain) - CultureInfo.GetCultureInfo("bg-BG"), // Bulgarian (Bulgaria) - CultureInfo.GetCultureInfo("de-DE"), // German (Germany) - CultureInfo.GetCultureInfo("es-ES"), // Spanish (Spain) - CultureInfo.GetCultureInfo("fi-FI"), // Finnish (Finland) - CultureInfo.GetCultureInfo("fo-FO"), // Faroese (Faroe Islands) - CultureInfo.GetCultureInfo("fr-FR"), // French (France) - CultureInfo.GetCultureInfo("hr-HR"), // Croatian (Croatia) - CultureInfo.GetCultureInfo("hu-HU"), // Hungarian (Hungary) - CultureInfo.GetCultureInfo("id-ID"), // Indonesian (Indonesia) - CultureInfo.GetCultureInfo("is-IS"), // Icelandic (Iceland) - CultureInfo.GetCultureInfo("it-IT"), // Italian (Italy) - CultureInfo.GetCultureInfo("lt-LT"), // Lithuanian (Lithuania) - CultureInfo.GetCultureInfo("lv-LV"), // Latvian (Latvia) - CultureInfo.GetCultureInfo("mg-MG"), // Malagasy (Madagascar) - CultureInfo.GetCultureInfo("mk-MK"), // Macedonian (North Macedonia) - CultureInfo.GetCultureInfo("mn-MN"), // Mongolian (Mongolia) - CultureInfo.GetCultureInfo("mt-MT"), // Maltese (Malta) - CultureInfo.GetCultureInfo("nl-NL"), // Dutch (Netherlands) - CultureInfo.GetCultureInfo("pl-PL"), // Polish (Poland) - CultureInfo.GetCultureInfo("pt-PT"), // Portuguese (Portugal) - CultureInfo.GetCultureInfo("ro-RO"), // Romanian (Romania) - CultureInfo.GetCultureInfo("ru-RU"), // Russian (Russia) - CultureInfo.GetCultureInfo("rw-RW"), // Kinyarwanda (Rwanda) - CultureInfo.GetCultureInfo("se-SE"), // Northern Sami (Sweden) - CultureInfo.GetCultureInfo("sk-SK"), // Slovak (Slovakia) - CultureInfo.GetCultureInfo("so-SO"), // Somali (Somalia) - CultureInfo.GetCultureInfo("th-TH"), // Thai (Thailand) - CultureInfo.GetCultureInfo("to-TO"), // Tongan (Tonga) - CultureInfo.GetCultureInfo("tr-TR"), // Turkish (Türkiye) - CultureInfo.GetCultureInfo("zh-Hans-CN"), // Chinese, Simplified (China mainland) - CultureInfo.GetCultureInfo("zh-Hant-CN"), // Chinese, Traditional (China mainland) - ]; + private static readonly INumericDataProvider Provider = INumericDataProvider.GetProvider(); public static IEnumerable GenerateScaledValues( IEnumerable? values = null, IEnumerable? scales = null, IEnumerable? modes = null) { - foreach (ScaleMode mode in modes ?? DefaultModes) - foreach (int value in values ?? DefaultValues) - foreach (int scale in scales ?? DefaultScales) + foreach (ScaleMode mode in modes ?? Provider.ScaleModes) + foreach (int value in values ?? Provider.IntegerValues) + foreach (int scale in scales ?? Provider.IntegerScales) yield return value.ToDecimal(scale, mode); } - public static IEnumerable GenerateRandomValues(int count = 100, int seed = default) + public static IEnumerable GenerateRandomValues(int? count = null, int seed = default) { + count ??= Provider.RandomCount; Random random = new(seed); for (int index = 0; index < count; index++) @@ -932,21 +64,22 @@ public static IEnumerable GenerateConstantValues() public static IEnumerable GenerateStaticValues() { - return StaticValues; + return Provider.DecimalValues; } public static IEnumerable GenerateCultures() { - return Cultures; + return Provider.Cultures; } public static IEnumerable GetMidpointRoundingModes() { - return Enum.GetValues(); + return Provider.RoundingModes; } - public static IEnumerable GenerateScaleValues(int min = MinScale, int max = MaxScale) + public static IEnumerable GenerateScaleValues(int? min = null, int? max = null) { - for (int scale = min; scale <= max; scale++) yield return scale; + for (int scale = min ?? Provider.MinScale; scale <= (max ?? Provider.MaxScale); scale++) + yield return scale; } } diff --git a/OnixLabs.Numerics/BigDecimal.Arithmetic.Pow.cs b/OnixLabs.Numerics/BigDecimal.Arithmetic.Pow.cs index c9addb3..51c2b0c 100644 --- a/OnixLabs.Numerics/BigDecimal.Arithmetic.Pow.cs +++ b/OnixLabs.Numerics/BigDecimal.Arithmetic.Pow.cs @@ -33,8 +33,11 @@ public static BigDecimal Pow(BigDecimal value, int exponent, MidpointRounding mo return exponent is 0 ? One : Zero; } - if (exponent is 0) return One; - if (exponent is 1) return value; + switch (exponent) + { + case 0: return One; + case 1: return value; + } BigDecimal result = Abs(value); int absExponent = int.Abs(exponent); diff --git a/OnixLabs.Numerics/BigDecimal.Arithmetic.Scale.cs b/OnixLabs.Numerics/BigDecimal.Arithmetic.Scale.cs index c9373c7..865ae4b 100644 --- a/OnixLabs.Numerics/BigDecimal.Arithmetic.Scale.cs +++ b/OnixLabs.Numerics/BigDecimal.Arithmetic.Scale.cs @@ -26,6 +26,7 @@ public readonly partial struct BigDecimal /// The scale to apply to the value. /// The mode to be used when the specified scale is less than the current scale. /// Returns a new value with the specified scale. + // ReSharper disable once MemberCanBePrivate.Global public static BigDecimal SetScale(BigDecimal value, int scale, MidpointRounding mode = default) { Require(scale >= 0, "Scale must be greater than or equal to zero.", nameof(scale)); diff --git a/OnixLabs.Numerics/BigDecimal.Arithmetic.Trim.cs b/OnixLabs.Numerics/BigDecimal.Arithmetic.Trim.cs index eb005ab..9f472b4 100644 --- a/OnixLabs.Numerics/BigDecimal.Arithmetic.Trim.cs +++ b/OnixLabs.Numerics/BigDecimal.Arithmetic.Trim.cs @@ -23,6 +23,7 @@ public readonly partial struct BigDecimal /// /// The value from which to trim trailing zeros. /// Returns a new excluding any trailing zeros. + // ReSharper disable once MemberCanBePrivate.Global public static BigDecimal TrimTrailingZeros(BigDecimal value) { if (IsZero(value)) return Zero; diff --git a/OnixLabs.Numerics/BigDecimal.Comparable.cs b/OnixLabs.Numerics/BigDecimal.Comparable.cs index 81ec318..a5e4113 100644 --- a/OnixLabs.Numerics/BigDecimal.Comparable.cs +++ b/OnixLabs.Numerics/BigDecimal.Comparable.cs @@ -32,6 +32,7 @@ public readonly partial struct BigDecimal /// /// An object to compare with this instance. /// Returns a value that indicates the relative order of the objects being compared. + // ReSharper disable once HeapView.BoxingAllocation public int CompareTo(object? obj) => BigDecimalOrdinalityComparer.Default.Compare(this, obj); /// diff --git a/OnixLabs.Numerics/BigDecimal.Convertible.cs b/OnixLabs.Numerics/BigDecimal.Convertible.cs index a79a537..e5e994d 100644 --- a/OnixLabs.Numerics/BigDecimal.Convertible.cs +++ b/OnixLabs.Numerics/BigDecimal.Convertible.cs @@ -135,5 +135,6 @@ public readonly partial struct BigDecimal /// The to which the value of this instance is converted /// An interface implementation that supplies culture-specific formatting information. /// Returns an instance of type conversionType whose value is equivalent to the value of this instance. + // ReSharper disable once HeapView.BoxingAllocation object IConvertible.ToType(Type conversionType, IFormatProvider? provider) => Convert.ChangeType(this, conversionType, provider); } diff --git a/OnixLabs.Numerics/BigDecimal.Equatable.cs b/OnixLabs.Numerics/BigDecimal.Equatable.cs index 45d7389..39549c3 100644 --- a/OnixLabs.Numerics/BigDecimal.Equatable.cs +++ b/OnixLabs.Numerics/BigDecimal.Equatable.cs @@ -32,6 +32,7 @@ public readonly partial struct BigDecimal /// The right-hand value to compare. /// The equality comparer to use to determine equality. /// Returns if the two specified instances are equal; otherwise, . + // ReSharper disable once MemberCanBePrivate.Global public static bool Equals(BigDecimal left, BigDecimal right, BigDecimalEqualityComparer comparer) => comparer.Equals(left, right); /// @@ -48,6 +49,7 @@ public readonly partial struct BigDecimal /// The other instance of to compare with the current instance. /// The equality comparer to use to determine equality. /// Returns if the current instance is equal to the specified other instance; otherwise, . + // ReSharper disable once MemberCanBePrivate.Global public bool Equals(BigDecimal other, BigDecimalEqualityComparer comparer) => Equals(this, other, comparer); /// diff --git a/OnixLabs.Numerics/BigDecimal.Parse.cs b/OnixLabs.Numerics/BigDecimal.Parse.cs index 3cae2c5..98e62e0 100644 --- a/OnixLabs.Numerics/BigDecimal.Parse.cs +++ b/OnixLabs.Numerics/BigDecimal.Parse.cs @@ -103,6 +103,7 @@ public static BigDecimal Parse(ReadOnlySpan value, NumberStyles style, IFo /// or the default value in the event that the specified value could not be parsed. /// /// Returns if the specified value was parsed successfully; otherwise, . + // ReSharper disable once MemberCanBePrivate.Global public static bool TryParse(ReadOnlySpan value, out BigDecimal result) => TryParse(value, DefaultCulture, out result); /// @@ -131,6 +132,7 @@ public static BigDecimal Parse(ReadOnlySpan value, NumberStyles style, IFo public static bool TryParse(ReadOnlySpan value, NumberStyles style, IFormatProvider? provider, out BigDecimal result) { CultureInfo info = provider as CultureInfo ?? DefaultCulture; + // ReSharper disable once HeapView.ObjectAllocation.Evident NumberInfoParser parser = new(style, info); if (parser.TryParse(value, out NumberInfo numberInfo)) diff --git a/OnixLabs.Numerics/BigDecimal.To.cs b/OnixLabs.Numerics/BigDecimal.To.cs index 3639b0f..093a930 100644 --- a/OnixLabs.Numerics/BigDecimal.To.cs +++ b/OnixLabs.Numerics/BigDecimal.To.cs @@ -45,12 +45,14 @@ public readonly partial struct BigDecimal /// The format to use, or null to use the default format. /// The provider to use to format the value. /// The value of the current instance in the specified format. + // ReSharper disable once MemberCanBePrivate.Global public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) { CultureInfo info = formatProvider as CultureInfo ?? DefaultCulture; if (!TryGetScaledNumberInfo(format, info.NumberFormat, out NumberInfo value)) return format.ToString(); + // ReSharper disable once HeapView.ObjectAllocation.Evident, HeapView.ObjectAllocation NumberInfoFormatter formatter = new(value, info, ['C', 'D', 'E', 'F', 'G', 'N', 'P']); return formatter.Format(format); } diff --git a/OnixLabs.Numerics/BigDecimal.cs b/OnixLabs.Numerics/BigDecimal.cs index 7671ae1..3ea6b12 100644 --- a/OnixLabs.Numerics/BigDecimal.cs +++ b/OnixLabs.Numerics/BigDecimal.cs @@ -21,6 +21,7 @@ namespace OnixLabs.Numerics; /// /// Represents an arbitrarily large signed decimal. /// +// ReSharper disable once HeapView.PossibleBoxingAllocation public readonly partial struct BigDecimal : IFloatingPoint, IValueEquatable, IValueComparable, IConvertible { /// diff --git a/OnixLabs.Numerics/BigDecimalEqualityComparer.cs b/OnixLabs.Numerics/BigDecimalEqualityComparer.cs index c97f013..28b4ea2 100644 --- a/OnixLabs.Numerics/BigDecimalEqualityComparer.cs +++ b/OnixLabs.Numerics/BigDecimalEqualityComparer.cs @@ -25,11 +25,13 @@ public abstract class BigDecimalEqualityComparer : IEqualityComparer /// /// Gets an equality comparer that strictly compares values. /// + // ReSharper disable once HeapView.ObjectAllocation.Evident public static readonly BigDecimalEqualityComparer Strict = new BigDecimalStrictEqualityComparer(); /// /// Gets an equality comparer that semantically compares values. /// + // ReSharper disable once HeapView.ObjectAllocation.Evident public static readonly BigDecimalEqualityComparer Semantic = new BigDecimalSemanticEqualityComparer(); /// diff --git a/OnixLabs.Numerics/BigDecimalOrdinalityComparer.cs b/OnixLabs.Numerics/BigDecimalOrdinalityComparer.cs index 9546a2d..7bcc063 100644 --- a/OnixLabs.Numerics/BigDecimalOrdinalityComparer.cs +++ b/OnixLabs.Numerics/BigDecimalOrdinalityComparer.cs @@ -26,6 +26,7 @@ public sealed class BigDecimalOrdinalityComparer : IComparer, ICompa /// /// Gets the default ordinal comparer for comparing values. /// + // ReSharper disable once HeapView.ObjectAllocation.Evident public static readonly BigDecimalOrdinalityComparer Default = new(); /// diff --git a/OnixLabs.Numerics/NumberInfo.Comparable.cs b/OnixLabs.Numerics/NumberInfo.Comparable.cs index 87af347..7c5f20b 100644 --- a/OnixLabs.Numerics/NumberInfo.Comparable.cs +++ b/OnixLabs.Numerics/NumberInfo.Comparable.cs @@ -41,6 +41,7 @@ public readonly partial struct NumberInfo /// /// An object to compare with this instance. /// Returns a value that indicates the relative order of the objects being compared. + // ReSharper disable once HeapView.BoxingAllocation public int CompareTo(object? obj) => NumberInfoOrdinalityComparer.Default.Compare(this, obj); /// diff --git a/OnixLabs.Numerics/NumberInfo.Equatable.cs b/OnixLabs.Numerics/NumberInfo.Equatable.cs index 4c25189..156d0b7 100644 --- a/OnixLabs.Numerics/NumberInfo.Equatable.cs +++ b/OnixLabs.Numerics/NumberInfo.Equatable.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; - namespace OnixLabs.Numerics; public readonly partial struct NumberInfo @@ -34,6 +32,7 @@ public readonly partial struct NumberInfo /// The right-hand value to compare. /// The equality comparer to use to determine equality. /// Returns if the two specified instances are equal; otherwise, . + // ReSharper disable once MemberCanBePrivate.Global public static bool Equals(NumberInfo left, NumberInfo right, NumberInfoEqualityComparer comparer) => comparer.Equals(left, right); /// @@ -50,6 +49,7 @@ public readonly partial struct NumberInfo /// The other instance of to compare with the current instance. /// The equality comparer to use to determine equality. /// Returns if the current instance is equal to the specified other instance; otherwise, . + // ReSharper disable once MemberCanBePrivate.Global public bool Equals(NumberInfo other, NumberInfoEqualityComparer comparer) => Equals(this, other, comparer); /// diff --git a/OnixLabs.Numerics/NumberInfo.Parse.cs b/OnixLabs.Numerics/NumberInfo.Parse.cs index 4ba6df7..724ee2f 100644 --- a/OnixLabs.Numerics/NumberInfo.Parse.cs +++ b/OnixLabs.Numerics/NumberInfo.Parse.cs @@ -51,6 +51,7 @@ public readonly partial struct NumberInfo /// A bitwise combination of number styles that can be present in the specified value. /// An object that provides culture-specific information about the specified value. /// Returns a new instance parsed from the specified value. + // ReSharper disable once MemberCanBePrivate.Global public static NumberInfo Parse(ReadOnlySpan value, NumberStyles style, IFormatProvider? provider = null) { CultureInfo info = provider as CultureInfo ?? DefaultCulture; @@ -103,6 +104,7 @@ public static NumberInfo Parse(ReadOnlySpan value, NumberStyles style, IFo /// or the default value in the event that the specified value could not be parsed. /// /// Returns if the specified value was parsed successfully; otherwise, . + // ReSharper disable once MemberCanBePrivate.Global public static bool TryParse(ReadOnlySpan value, out NumberInfo result) => TryParse(value, DefaultCulture, out result); /// @@ -128,9 +130,11 @@ public static NumberInfo Parse(ReadOnlySpan value, NumberStyles style, IFo /// or the default value in the event that the specified value could not be parsed. /// /// Returns if the specified value was parsed successfully; otherwise, . + // ReSharper disable once MemberCanBePrivate.Global public static bool TryParse(ReadOnlySpan value, NumberStyles style, IFormatProvider? provider, out NumberInfo result) { CultureInfo info = provider as CultureInfo ?? DefaultCulture; + // ReSharper disable once HeapView.ObjectAllocation.Evident NumberInfoParser parser = new(style, info); return parser.TryParse(value, out result); } diff --git a/OnixLabs.Numerics/NumberInfo.To.cs b/OnixLabs.Numerics/NumberInfo.To.cs index e5eaf86..e4d9032 100644 --- a/OnixLabs.Numerics/NumberInfo.To.cs +++ b/OnixLabs.Numerics/NumberInfo.To.cs @@ -39,9 +39,11 @@ public readonly partial struct NumberInfo /// The format to use, or null to use the default format. /// The provider to use to format the value. /// The value of the current instance in the specified format. + // ReSharper disable once MemberCanBePrivate.Global public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) { CultureInfo info = formatProvider as CultureInfo ?? DefaultCulture; + // ReSharper disable once HeapView.ObjectAllocation.Evident, HeapView.ObjectAllocation NumberInfoFormatter formatter = new(this, info, ['E', 'G']); return formatter.Format(format); } diff --git a/OnixLabs.Numerics/NumberInfoEqualityComparer.cs b/OnixLabs.Numerics/NumberInfoEqualityComparer.cs index e904bdd..ecd0431 100644 --- a/OnixLabs.Numerics/NumberInfoEqualityComparer.cs +++ b/OnixLabs.Numerics/NumberInfoEqualityComparer.cs @@ -26,11 +26,13 @@ public abstract class NumberInfoEqualityComparer : IEqualityComparer /// /// Gets a that strictly compares values. /// + // ReSharper disable once HeapView.ObjectAllocation.Evident public static readonly NumberInfoEqualityComparer Strict = new NumberInfoStrictEqualityComparer(); /// /// Gets a that semantically compares values. /// + // ReSharper disable once HeapView.ObjectAllocation.Evident public static readonly NumberInfoEqualityComparer Semantic = new NumberInfoSemanticEqualityComparer(); /// diff --git a/OnixLabs.Numerics/NumberInfoFormatter.FormatCurrency.cs b/OnixLabs.Numerics/NumberInfoFormatter.FormatCurrency.cs index f159b14..48acc14 100644 --- a/OnixLabs.Numerics/NumberInfoFormatter.FormatCurrency.cs +++ b/OnixLabs.Numerics/NumberInfoFormatter.FormatCurrency.cs @@ -46,10 +46,10 @@ private void FormatCurrencyPositivePattern() builder.Append(numberFormat.CurrencySymbol); break; case 2: // $ n - builder.Prepend(numberFormat.CurrencySymbol, Whitespace); + builder.Prepend(Whitespace).Prepend(numberFormat.CurrencySymbol); break; case 3: // n $ - builder.Append(Whitespace, numberFormat.CurrencySymbol); + builder.Append(Whitespace).Append(numberFormat.CurrencySymbol); break; } } @@ -67,10 +67,10 @@ private void FormatCurrencyNegativePattern() builder.Prepend(numberFormat.CurrencySymbol).Wrap(LeadingParenthesis, TrailingParenthesis); break; case 1: // -$n - builder.Prepend(numberFormat.NegativeSign, numberFormat.CurrencySymbol); + builder.Prepend(numberFormat.CurrencySymbol).Prepend(numberFormat.NegativeSign); break; case 2: // $-n - builder.Prepend(numberFormat.CurrencySymbol, numberFormat.NegativeSign); + builder.Prepend(numberFormat.NegativeSign).Prepend(numberFormat.CurrencySymbol); break; case 3: // $n- builder.Prepend(numberFormat.CurrencySymbol).Append(numberFormat.NegativeSign); @@ -82,37 +82,37 @@ private void FormatCurrencyNegativePattern() builder.Prepend(numberFormat.NegativeSign).Append(numberFormat.CurrencySymbol); break; case 6: // n-$ - builder.Append(numberFormat.NegativeSign, numberFormat.CurrencySymbol); + builder.Append(numberFormat.NegativeSign).Append(numberFormat.CurrencySymbol); break; case 7: // n$- - builder.Append(numberFormat.CurrencySymbol, numberFormat.NegativeSign); + builder.Append(numberFormat.CurrencySymbol).Append(numberFormat.NegativeSign); break; case 8: // -n $ - builder.Prepend(numberFormat.NegativeSign).Append(Whitespace, numberFormat.CurrencySymbol); + builder.Prepend(numberFormat.NegativeSign).Append(Whitespace).Append(numberFormat.CurrencySymbol); break; case 9: // -$ n - builder.Prepend(numberFormat.NegativeSign, numberFormat.CurrencySymbol, Whitespace); + builder.Prepend(Whitespace).Prepend(numberFormat.CurrencySymbol).Prepend(numberFormat.NegativeSign); break; case 10: // n $- - builder.Append(Whitespace, numberFormat.CurrencySymbol, numberFormat.NegativeSign); + builder.Append(Whitespace).Append(numberFormat.CurrencySymbol).Append(numberFormat.NegativeSign); break; case 11: // $ n- - builder.Prepend(numberFormat.CurrencySymbol, Whitespace).Append(numberFormat.NegativeSign); + builder.Prepend(Whitespace).Prepend(numberFormat.CurrencySymbol).Append(numberFormat.NegativeSign); break; case 12: // $ -n - builder.Prepend(numberFormat.CurrencySymbol, Whitespace, numberFormat.NegativeSign); + builder.Prepend(numberFormat.NegativeSign).Prepend(Whitespace).Prepend(numberFormat.CurrencySymbol); break; case 13: // n- $ - builder.Append(numberFormat.NegativeSign, Whitespace, numberFormat.CurrencySymbol); + builder.Append(numberFormat.NegativeSign).Append(Whitespace).Append(numberFormat.CurrencySymbol); break; case 14: // ($ n) - builder.Prepend(numberFormat.CurrencySymbol, Whitespace).Wrap(LeadingParenthesis, TrailingParenthesis); + builder.Prepend(Whitespace).Prepend(numberFormat.CurrencySymbol).Wrap(LeadingParenthesis, TrailingParenthesis); break; case 15: // (n $) - builder.Append(Whitespace, numberFormat.CurrencySymbol).Wrap(LeadingParenthesis, TrailingParenthesis); + builder.Append(Whitespace).Append(numberFormat.CurrencySymbol).Wrap(LeadingParenthesis, TrailingParenthesis); break; case 16: // $- n - builder.Prepend(numberFormat.CurrencySymbol, numberFormat.NegativeSign, Whitespace); + builder.Prepend(Whitespace).Prepend(numberFormat.NegativeSign).Prepend(numberFormat.CurrencySymbol); break; } } diff --git a/OnixLabs.Numerics/NumberInfoFormatter.FormatExponential.cs b/OnixLabs.Numerics/NumberInfoFormatter.FormatExponential.cs index 50a2a90..ad6d5c2 100644 --- a/OnixLabs.Numerics/NumberInfoFormatter.FormatExponential.cs +++ b/OnixLabs.Numerics/NumberInfoFormatter.FormatExponential.cs @@ -25,7 +25,7 @@ internal sealed partial class NumberInfoFormatter /// The exponentiation specifier, which is either an uppercase E, or lowercase e. private void FormatExponential(char specifier) { - builder.Append(BigInteger.Abs(value.UnscaledValue)); + builder.Append(BigInteger.Abs(value.UnscaledValue).ToString()); if (value == NumberInfo.Zero) return; @@ -35,7 +35,7 @@ private void FormatExponential(char specifier) if (exponent == 0) return; string sign = exponent > 0 ? numberFormat.PositiveSign : numberFormat.NegativeSign; - builder.Append(specifier, sign, int.Abs(exponent)); + builder.Append(specifier).Append(sign).Append(int.Abs(exponent)); if (value.UnscaledValue < BigInteger.Zero) builder.Prepend(numberFormat.NegativeSign); } diff --git a/OnixLabs.Numerics/NumberInfoFormatter.FormatNumber.cs b/OnixLabs.Numerics/NumberInfoFormatter.FormatNumber.cs index 3932b54..b3ea5d7 100644 --- a/OnixLabs.Numerics/NumberInfoFormatter.FormatNumber.cs +++ b/OnixLabs.Numerics/NumberInfoFormatter.FormatNumber.cs @@ -45,13 +45,13 @@ private void FormatNumberNegativePattern() builder.Prepend(numberFormat.NegativeSign); break; case 2: // - n - builder.Prepend(numberFormat.NegativeSign, Whitespace); + builder.Prepend(Whitespace).Prepend(numberFormat.NegativeSign); break; case 3: // n- builder.Append(numberFormat.NegativeSign); break; case 4: // n - - builder.Append(Whitespace, numberFormat.NegativeSign); + builder.Append(Whitespace).Append(numberFormat.NegativeSign); break; } } diff --git a/OnixLabs.Numerics/NumberInfoFormatter.FormatPercentage.cs b/OnixLabs.Numerics/NumberInfoFormatter.FormatPercentage.cs index ab85fdb..4d0c24c 100644 --- a/OnixLabs.Numerics/NumberInfoFormatter.FormatPercentage.cs +++ b/OnixLabs.Numerics/NumberInfoFormatter.FormatPercentage.cs @@ -40,7 +40,7 @@ private void FormatPercentPositivePattern() switch (numberFormat.PercentPositivePattern) { case 0: // n % - builder.Append(Whitespace, numberFormat.PercentSymbol); + builder.Append(Whitespace).Append(numberFormat.PercentSymbol); break; case 1: // n% builder.Append(numberFormat.PercentSymbol); @@ -49,7 +49,7 @@ private void FormatPercentPositivePattern() builder.Prepend(numberFormat.PercentSymbol); break; case 3: // % n - builder.Prepend(numberFormat.PercentSymbol, Whitespace); + builder.Prepend(Whitespace).Prepend(numberFormat.PercentSymbol); break; } } @@ -64,40 +64,40 @@ private void FormatPercentNegativePattern() switch (numberFormat.PercentNegativePattern) { case 0: // -n % - builder.Prepend(numberFormat.NegativeSign).Append(Whitespace, numberFormat.PercentSymbol); + builder.Prepend(numberFormat.NegativeSign).Append(Whitespace).Append(numberFormat.PercentSymbol); break; case 1: // -n% builder.Prepend(numberFormat.NegativeSign).Append(numberFormat.PercentSymbol); break; case 2: // -%n - builder.Prepend(numberFormat.NegativeSign, numberFormat.PercentSymbol); + builder.Prepend(numberFormat.PercentSymbol).Prepend(numberFormat.NegativeSign); break; case 3: // %-n - builder.Prepend(numberFormat.PercentSymbol, numberFormat.NegativeSign); + builder.Prepend(numberFormat.NegativeSign).Prepend(numberFormat.PercentSymbol); break; case 4: // %n- builder.Prepend(numberFormat.PercentSymbol).Append(numberFormat.NegativeSign); break; case 5: // n-% - builder.Append(numberFormat.NegativeSign, numberFormat.PercentSymbol); + builder.Append(numberFormat.NegativeSign).Append(numberFormat.PercentSymbol); break; case 6: // n%- - builder.Append(numberFormat.PercentSymbol, numberFormat.NegativeSign); + builder.Append(numberFormat.PercentSymbol).Append(numberFormat.NegativeSign); break; case 7: // -% n - builder.Prepend(numberFormat.NegativeSign, numberFormat.PercentSymbol, Whitespace); + builder.Prepend(Whitespace).Prepend(numberFormat.PercentSymbol).Prepend(numberFormat.NegativeSign); break; case 8: // n %- - builder.Append(Whitespace, numberFormat.PercentSymbol, numberFormat.NegativeSign); + builder.Append(Whitespace).Append(numberFormat.PercentSymbol).Append(numberFormat.NegativeSign); break; case 9: // % n- - builder.Prepend(numberFormat.PercentSymbol, Whitespace).Append(numberFormat.NegativeSign); + builder.Prepend(Whitespace).Prepend(numberFormat.PercentSymbol).Append(numberFormat.NegativeSign); break; case 10: // % -n - builder.Prepend(numberFormat.PercentSymbol, Whitespace, numberFormat.NegativeSign); + builder.Prepend(numberFormat.NegativeSign).Prepend(Whitespace).Prepend(numberFormat.PercentSymbol); break; case 11: // n- % - builder.Append(numberFormat.NegativeSign, Whitespace, numberFormat.PercentSymbol); + builder.Append(numberFormat.NegativeSign).Append(Whitespace).Append(numberFormat.PercentSymbol); break; } } diff --git a/OnixLabs.Numerics/NumberInfoFormatter.cs b/OnixLabs.Numerics/NumberInfoFormatter.cs index d32b864..320169f 100644 --- a/OnixLabs.Numerics/NumberInfoFormatter.cs +++ b/OnixLabs.Numerics/NumberInfoFormatter.cs @@ -18,7 +18,6 @@ using System.Linq; using System.Numerics; using System.Text; -using OnixLabs.Core.Text; namespace OnixLabs.Numerics; @@ -31,11 +30,11 @@ namespace OnixLabs.Numerics; internal sealed partial class NumberInfoFormatter(NumberInfo value, IFormatProvider formatProvider, IEnumerable allowedFormats) { internal const char DefaultFormat = 'G'; - private const char LeadingParenthesis = '('; private const char TrailingParenthesis = ')'; private const char Whitespace = ' '; + // ReSharper disable once HeapView.ObjectAllocation.Evident private readonly StringBuilder builder = new(); private readonly NumberFormatInfo numberFormat = NumberFormatInfo.GetInstance(formatProvider); private readonly IEnumerable allowedFormats = allowedFormats.Select(char.ToUpperInvariant); @@ -89,7 +88,7 @@ public string Format(ReadOnlySpan format) /// The separator that separates each number group. private void FormatInteger(IReadOnlyList grouping, string separator = "") { - builder.Append(BigInteger.Abs(value.Integer)); + builder.Append(BigInteger.Abs(value.Integer).ToString()); if (grouping.Count == 0) return; @@ -119,6 +118,6 @@ private void FormatInteger(IReadOnlyList grouping, string separator = "") /// The separator that separates the integral and fractional components. private void FormatFraction(string separator) { - if (value.Scale > 0) builder.Append(separator, value.Fraction.ToString().PadLeft(value.Scale, '0')); + if (value.Scale > 0) builder.Append(separator).Append(value.Fraction.ToString().PadLeft(value.Scale, '0')); } } diff --git a/OnixLabs.Numerics/NumberInfoOrdinalityComparer.cs b/OnixLabs.Numerics/NumberInfoOrdinalityComparer.cs index 51cf8a4..6ab21f0 100644 --- a/OnixLabs.Numerics/NumberInfoOrdinalityComparer.cs +++ b/OnixLabs.Numerics/NumberInfoOrdinalityComparer.cs @@ -27,6 +27,7 @@ public sealed class NumberInfoOrdinalityComparer : IComparer, ICompa /// /// Gets the default ordinal comparer for comparing values. /// + // ReSharper disable once HeapView.ObjectAllocation.Evident public static readonly NumberInfoOrdinalityComparer Default = new(); /// diff --git a/OnixLabs.Numerics/NumberInfoParser.Sanitize.cs b/OnixLabs.Numerics/NumberInfoParser.Sanitize.cs index 2bc0c66..5193885 100644 --- a/OnixLabs.Numerics/NumberInfoParser.Sanitize.cs +++ b/OnixLabs.Numerics/NumberInfoParser.Sanitize.cs @@ -24,7 +24,7 @@ private bool TryTrimLeadingWhitespace(ref ReadOnlySpan value) ReadOnlySpan result = value.TrimStart(); if (result.SequenceEqual(value)) return true; - if (!style.HasFlag(AllowLeadingWhite)) return false; + if ((style & AllowLeadingWhite) is 0) return false; value = result; return true; @@ -35,7 +35,7 @@ private bool TryTrimTrailingWhitespace(ref ReadOnlySpan value) ReadOnlySpan result = value.TrimEnd(); if (result.SequenceEqual(value)) return true; - if (!style.HasFlag(AllowTrailingWhite)) return false; + if ((style & AllowTrailingWhite) is 0) return false; value = result; return true; @@ -46,7 +46,7 @@ private bool TryTrimLeadingCurrencySymbol(ref ReadOnlySpan value) ReadOnlySpan result = value.TrimStart(numberFormat.CurrencySymbol); if (result.SequenceEqual(value)) return true; - if (!style.HasFlag(AllowCurrencySymbol)) return false; + if ((style & AllowCurrencySymbol) is 0) return false; value = result; return true; @@ -57,7 +57,7 @@ private bool TryTrimTrailingCurrencySymbol(ref ReadOnlySpan value) ReadOnlySpan result = value.TrimEnd(numberFormat.CurrencySymbol); if (result.SequenceEqual(value)) return true; - if (!style.HasFlag(AllowCurrencySymbol)) return false; + if ((style & AllowCurrencySymbol) is 0) return false; value = result; return true; @@ -68,7 +68,7 @@ private bool TryTrimLeadingPositiveSign(ref ReadOnlySpan value, out bool h hasLeadingPositiveSign = value.StartsWith(numberFormat.PositiveSign, Comparison); if (!hasLeadingPositiveSign) return true; - if (!style.HasFlag(AllowLeadingSign)) return false; + if ((style & AllowLeadingSign) is 0) return false; value = value.TrimStart(numberFormat.PositiveSign); return true; @@ -79,7 +79,7 @@ private bool TryTrimTrailingPositiveSign(ref ReadOnlySpan value, out bool hasTrailingPositiveSign = value.EndsWith(numberFormat.PositiveSign, Comparison); if (!hasTrailingPositiveSign) return true; - if (!style.HasFlag(AllowTrailingSign)) return false; + if ((style & AllowTrailingSign) is 0) return false; value = value.TrimStart(numberFormat.PositiveSign); return true; @@ -90,7 +90,7 @@ private bool TryTrimLeadingNegativeSign(ref ReadOnlySpan value, out bool h hasLeadingNegativeSign = value.StartsWith(numberFormat.NegativeSign, Comparison); if (!hasLeadingNegativeSign) return true; - if (!style.HasFlag(AllowLeadingSign)) return false; + if ((style & AllowLeadingSign) is 0) return false; value = value.TrimStart(numberFormat.NegativeSign); return true; @@ -101,7 +101,7 @@ private bool TryTrimTrailingNegativeSign(ref ReadOnlySpan value, out bool hasTrailingNegativeSign = value.EndsWith(numberFormat.NegativeSign, Comparison); if (!hasTrailingNegativeSign) return true; - if (!style.HasFlag(AllowTrailingSign)) return false; + if ((style & AllowTrailingSign) is 0) return false; value = value.TrimStart(numberFormat.NegativeSign); return true; @@ -112,8 +112,9 @@ private bool TryTrimParentheses(ref ReadOnlySpan value, out bool hasParent hasParentheses = false; bool hasLeadingParenthesis = value.StartsWith(LeadingParenthesis, Comparison); bool hasTrailingParenthesis = value.EndsWith(TrailingParenthesis, Comparison); - bool areParenthesesAllowed = style.HasFlag(AllowParentheses); + bool areParenthesesAllowed = (style & AllowParentheses) is not 0; + // ReSharper disable once InvertIf if (hasLeadingParenthesis && hasTrailingParenthesis && areParenthesesAllowed) { hasParentheses = true; @@ -137,6 +138,6 @@ private bool TryTrimExponent(ref ReadOnlySpan value, out int exponent) ReadOnlySpan chars = value[(index + 1)..]; value = value[..index]; - return int.TryParse(chars, culture, out exponent) && style.HasFlag(AllowExponent); + return int.TryParse(chars, culture, out exponent) && (style & AllowExponent) is not 0; } } diff --git a/OnixLabs.Numerics/NumberInfoParser.SanitizeCurrency.cs b/OnixLabs.Numerics/NumberInfoParser.SanitizeCurrency.cs index 930cb4c..cfab549 100644 --- a/OnixLabs.Numerics/NumberInfoParser.SanitizeCurrency.cs +++ b/OnixLabs.Numerics/NumberInfoParser.SanitizeCurrency.cs @@ -13,7 +13,6 @@ // limitations under the License. using System; -using System.Linq; namespace OnixLabs.Numerics; @@ -129,8 +128,11 @@ private bool TrySanitizeCurrency(ref ReadOnlySpan value, out int sign) break; } - bool[] values = [hasParentheses, hasLeadingNegativeSign, hasTrailingNegativeSign]; - if (values.Count(value => value) > 1) return false; + int count = 0; + if (hasParentheses) count++; + if (hasLeadingNegativeSign) count++; + if (hasTrailingNegativeSign) count++; + if (count > 1) return false; if (hasParentheses || hasLeadingNegativeSign || hasTrailingNegativeSign) sign = -1; return true; diff --git a/OnixLabs.Numerics/NumberInfoParser.SanitizeNumber.cs b/OnixLabs.Numerics/NumberInfoParser.SanitizeNumber.cs index 55ca83a..dc83a6c 100644 --- a/OnixLabs.Numerics/NumberInfoParser.SanitizeNumber.cs +++ b/OnixLabs.Numerics/NumberInfoParser.SanitizeNumber.cs @@ -13,7 +13,6 @@ // limitations under the License. using System; -using System.Linq; namespace OnixLabs.Numerics; @@ -67,8 +66,13 @@ private bool TrySanitizeNumber(ref ReadOnlySpan value, out int sign, out i // Trim whitespace that appears between the number and an exponent if (!TryTrimTrailingWhitespace(ref value)) return false; - bool[] values = [hasParentheses, hasLeadingPositiveSign, hasLeadingNegativeSign, hasTrailingPositiveSign, hasTrailingNegativeSign]; - if (values.Count(value => value) > 1) return false; + int count = 0; + if (hasParentheses) count++; + if (hasLeadingPositiveSign) count++; + if (hasLeadingNegativeSign) count++; + if (hasTrailingPositiveSign) count++; + if (hasTrailingNegativeSign) count++; + if (count > 1) return false; if (hasParentheses || hasLeadingNegativeSign || hasTrailingNegativeSign) sign = -1; return true; diff --git a/OnixLabs.Numerics/NumberInfoParser.cs b/OnixLabs.Numerics/NumberInfoParser.cs index 1da1248..f34a47d 100644 --- a/OnixLabs.Numerics/NumberInfoParser.cs +++ b/OnixLabs.Numerics/NumberInfoParser.cs @@ -37,8 +37,8 @@ public bool TryParse(ReadOnlySpan value, out NumberInfo result) // Disallow the following number styles as they are unsupported. if (style == None) return false; - if (style.HasFlag(AllowHexSpecifier)) return false; - if (style.HasFlag(AllowBinarySpecifier)) return false; + if ((style & AllowHexSpecifier) is not 0) return false; + if ((style & AllowBinarySpecifier) is not 0) return false; // Special handling for sanitization of currency values. if (style == Currency) @@ -81,35 +81,36 @@ private bool TryGetNumberInfo(ref ReadOnlySpan value, out NumberInfo resul if (value.StartsWith(numberFormat.NumberGroupSeparator)) { - if (!style.HasFlag(AllowThousands)) return false; + if ((style & AllowThousands) is 0) return false; value = value.TrimStart(numberFormat.NumberGroupSeparator); continue; } if (value.StartsWith(numberFormat.CurrencyGroupSeparator)) { - if (!style.HasFlag(AllowThousands)) return false; + if ((style & AllowThousands) is 0) return false; value = value.TrimStart(numberFormat.CurrencyGroupSeparator); continue; } if (value.StartsWith(numberFormat.NumberDecimalSeparator)) { - if (hasDecimalPoint || !style.HasFlag(AllowDecimalPoint)) return false; + if (hasDecimalPoint || (style & AllowDecimalPoint) is 0) return false; hasDecimalPoint = true; value = value.TrimStart(numberFormat.NumberDecimalSeparator); continue; } + // ReSharper disable once InvertIf if (value.StartsWith(numberFormat.CurrencyDecimalSeparator)) { - if (hasDecimalPoint || !style.HasFlag(AllowDecimalPoint)) return false; + if (hasDecimalPoint || (style & AllowDecimalPoint) is 0) return false; hasDecimalPoint = true; value = value.TrimStart(numberFormat.CurrencyDecimalSeparator); continue; } - // If we reach this point, the start of the string isn't an ascii digit, thousand or decimal separator, therefore false. + // If we reach this point, the start of the string isn't an ascii digit, thousands or decimal separator, therefore false. return false; } diff --git a/OnixLabs.Numerics/NumericsExtensions.cs b/OnixLabs.Numerics/NumericsExtensions.cs index 5daf9c2..907af93 100644 --- a/OnixLabs.Numerics/NumericsExtensions.cs +++ b/OnixLabs.Numerics/NumericsExtensions.cs @@ -43,6 +43,7 @@ public static BigInteger GetUnscaledValue(this decimal value) { const int significandSize = 13; int[] significandBits = decimal.GetBits(value); + // ReSharper disable once HeapView.ObjectAllocation.Evident byte[] significandBytes = new byte[significandSize]; Buffer.BlockCopy(significandBits, 0, significandBytes, 0, significandSize); BigInteger result = new(significandBytes); diff --git a/OnixLabs.Numerics/OnixLabs.Numerics.csproj b/OnixLabs.Numerics/OnixLabs.Numerics.csproj index ed51c87..8951821 100644 --- a/OnixLabs.Numerics/OnixLabs.Numerics.csproj +++ b/OnixLabs.Numerics/OnixLabs.Numerics.csproj @@ -4,13 +4,13 @@ OnixLabs.Numerics ONIXLabs ONIXLabs Numerics API for .NET - 8.16.0 + 9.0.0 en enable true Copyright © ONIXLabs 2020 https://github.com/onix-labs/onixlabs-dotnet - 8.16.0 + 9.0.0 12 diff --git a/OnixLabs.Playground/Program.cs b/OnixLabs.Playground/Program.cs index d629839..0779002 100644 --- a/OnixLabs.Playground/Program.cs +++ b/OnixLabs.Playground/Program.cs @@ -12,11 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + namespace OnixLabs.Playground; internal static class Program { private static void Main() { + try + { + InvalidOperationException ex = new("Hello"); + + foreach (PropertyInfo property in ex.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + Console.WriteLine($"{property.Name} = {property.GetValue(ex)}"); + } + + throw ex; + } + catch (Exception ex) + { + foreach (PropertyInfo property in ex.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + Console.WriteLine($"{property.Name} = {property.GetValue(ex)}"); + } + } + } } diff --git a/OnixLabs.Security.Cryptography.UnitTests.Data/BinaryConvertible.cs b/OnixLabs.Security.Cryptography.UnitTests.Data/BinaryConvertible.cs new file mode 100644 index 0000000..4a9f8e1 --- /dev/null +++ b/OnixLabs.Security.Cryptography.UnitTests.Data/BinaryConvertible.cs @@ -0,0 +1,23 @@ +// 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 OnixLabs.Core; + +namespace OnixLabs.Security.Cryptography.UnitTests.Data; + +public class BinaryConvertible(byte[] data) : ISpanBinaryConvertible +{ + public byte[] ToByteArray() => data; + public ReadOnlySpan ToReadOnlySpan() => data; +} diff --git a/OnixLabs.Security.Cryptography.UnitTests/ExtensionTests.cs b/OnixLabs.Security.Cryptography.UnitTests/ExtensionTests.cs new file mode 100644 index 0000000..a38fa7c --- /dev/null +++ b/OnixLabs.Security.Cryptography.UnitTests/ExtensionTests.cs @@ -0,0 +1,137 @@ +// 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.Security.Cryptography.UnitTests; + +public sealed class ExtensionsTests +{ + [Fact(DisplayName = "Byte array ToHash should produce expected Hash instance")] + public void ByteArrayToHashShouldProduceExpectedHash() + { + // Given + byte[] value = [1, 2, 3, 4]; + Hash expected = new(value); + + // When + Hash actual = value.ToHash(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "ReadOnlySpan ToHash should produce expected Hash instance")] + public void ReadOnlySpanToHashShouldProduceExpectedHash() + { + // Given + byte[] valueArray = [1, 2, 3, 4]; + ReadOnlySpan value = valueArray; + Hash expected = new(valueArray); + + // When + Hash actual = value.ToHash(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Byte array ToSecret should produce expected Secret instance")] + public void ByteArrayToSecretShouldProduceExpectedSecret() + { + // Given + byte[] value = [1, 2, 3, 4]; + Secret expected = new(value); + + // When + Secret actual = value.ToSecret(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "ReadOnlySpan ToSecret should produce expected Secret instance")] + public void ReadOnlySpanToSecretShouldProduceExpectedSecret() + { + // Given + byte[] valueArray = [1, 2, 3, 4]; + ReadOnlySpan value = valueArray; + Secret expected = new(valueArray); + + // When + Secret actual = value.ToSecret(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Byte array ToHash should handle empty array")] + public void ByteArrayToHashShouldHandleEmptyArray() + { + // Given + byte[] value = []; + Hash expected = new(value); + + // When + Hash actual = value.ToHash(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "ReadOnlySpan ToHash should handle empty span")] + public void ReadOnlySpanToHashShouldHandleEmptySpan() + { + // Given + byte[] valueArray = []; + ReadOnlySpan value = valueArray; + Hash expected = new(valueArray); + + // When + Hash actual = value.ToHash(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Byte array ToSecret should handle empty array")] + public void ByteArrayToSecretShouldHandleEmptyArray() + { + // Given + byte[] value = []; + Secret expected = new(value); + + // When + Secret actual = value.ToSecret(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "ReadOnlySpan ToSecret should handle empty span")] + public void ReadOnlySpanToSecretShouldHandleEmptySpan() + { + // Given + byte[] valueArray = []; + ReadOnlySpan value = valueArray; + Secret expected = new(valueArray); + + // When + Secret actual = value.ToSecret(); + + // Then + Assert.Equal(expected, actual); + } +} diff --git a/OnixLabs.Security.Cryptography.UnitTests/HashAlgorithmExtensionTests.cs b/OnixLabs.Security.Cryptography.UnitTests/HashAlgorithmExtensionTests.cs index ae0b4de..f75f27c 100644 --- a/OnixLabs.Security.Cryptography.UnitTests/HashAlgorithmExtensionTests.cs +++ b/OnixLabs.Security.Cryptography.UnitTests/HashAlgorithmExtensionTests.cs @@ -15,8 +15,8 @@ using System; using System.IO; using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; +using OnixLabs.Core; +using OnixLabs.Security.Cryptography.UnitTests.Data; using Xunit; namespace OnixLabs.Security.Cryptography.UnitTests; @@ -38,19 +38,143 @@ public void HashAlgorithmComputeHashShouldProduceExpectedResultWithTwoRounds() Assert.Equal(expected, actual); } - [Fact(DisplayName = "HashAlgorithm.ComputeHashAsync should produce the expected result with two rounds")] - public async Task HashAlgorithmComputeHashAsyncShouldProduceExpectedResultWithTwoRounds() + [Fact(DisplayName = "HashAlgorithm.ComputeHash should produce the expected result with one round")] + public void HashAlgorithmComputeHashShouldProduceExpectedResultWithOneRound() { // Given using HashAlgorithm algorithm = SHA256.Create(); - Stream data = new MemoryStream("abc123"u8.ToArray()); + const string expected = "6ca13d52ca70c883e0f0bb101e425a89e8624de51db2d2392593af6a84118090"; + + // When + byte[] bytes = algorithm.ComputeHash("abc123", rounds: 1); + string actual = Convert.ToHexString(bytes).ToLower(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "HashAlgorithm.ComputeHash should throw CryptographicException for zero rounds")] + public void HashAlgorithmComputeHashShouldThrowForZeroRounds() + { + // Given + using HashAlgorithm algorithm = SHA256.Create(); + + // Then + Assert.Throws(() => algorithm.ComputeHash("abc123", rounds: 0)); + } + + [Fact(DisplayName = "HashAlgorithm.ComputeHash with offset and count should produce the expected result")] + public void HashAlgorithmComputeHashWithOffsetAndCountShouldProduceExpectedResult() + { + // Given + using HashAlgorithm algorithm = SHA256.Create(); + byte[] data = "abcdefghijklmnopqrstuvwxyz".ToByteArray(); + const string expected = "c8507de92a6160c0437f47cfa8d1bfab6fa77137b466d8f0685e393b06ea5089"; + + // When + byte[] bytes = algorithm.ComputeHash(data, offset: 1, count: 24, rounds: 2); + string actual = Convert.ToHexString(bytes).ToLower(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "HashAlgorithm.ComputeHash should produce expected result from ReadOnlySpan")] + public void HashAlgorithmComputeHashShouldProduceExpectedResultFromReadOnlySpanChar() + { + // Given + using HashAlgorithm algorithm = SHA256.Create(); + const string input = "abc123"; + const string expected = "efaaeb3b1d1d85e8587ef0527ca43b9575ce8149ba1ee41583d3d19bd130daf8"; + + // When + byte[] bytes = algorithm.ComputeHash(input.AsSpan(), rounds: 2); + string actual = Convert.ToHexString(bytes).ToLower(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "HashAlgorithm.ComputeHash should produce expected result from IBinaryConvertible")] + public void HashAlgorithmComputeHashShouldProduceExpectedResultFromIBinaryConvertible() + { + // Given + using HashAlgorithm algorithm = SHA256.Create(); + BinaryConvertible data = new("abc123".ToByteArray()); + const string expected = "efaaeb3b1d1d85e8587ef0527ca43b9575ce8149ba1ee41583d3d19bd130daf8"; + + // When + byte[] bytes = algorithm.ComputeHash(data, rounds: 2); + string actual = Convert.ToHexString(bytes).ToLower(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "HashAlgorithm.ComputeHash should produce expected result from ISpanBinaryConvertible")] + public void HashAlgorithmComputeHashShouldProduceExpectedResultFromISpanBinaryConvertible() + { + // Given + using HashAlgorithm algorithm = SHA256.Create(); + BinaryConvertible data = new("abc123".ToByteArray()); + const string expected = "efaaeb3b1d1d85e8587ef0527ca43b9575ce8149ba1ee41583d3d19bd130daf8"; + + // When + byte[] bytes = algorithm.ComputeHash(data, rounds: 2); + string actual = Convert.ToHexString(bytes).ToLower(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "HashAlgorithm.ComputeHash should produce expected result from Stream")] + public void HashAlgorithmComputeHashShouldProduceExpectedResultFromStream() + { + // Given + using HashAlgorithm algorithm = SHA256.Create(); + using MemoryStream stream = new("abc123".ToByteArray()); const string expected = "efaaeb3b1d1d85e8587ef0527ca43b9575ce8149ba1ee41583d3d19bd130daf8"; // When - byte[] bytes = await algorithm.ComputeHashAsync(data, 2); + byte[] bytes = algorithm.ComputeHash(stream, rounds: 2); string actual = Convert.ToHexString(bytes).ToLower(); // Then Assert.Equal(expected, actual); } + + [Fact(DisplayName = "HashAlgorithm.TryComputeHash should return true and produce expected result")] + public void HashAlgorithmTryComputeHashShouldReturnTrueAndProduceExpectedResult() + { + // Given + using HashAlgorithm algorithm = SHA256.Create(); + byte[] data = "abc123".ToByteArray(); + Span destination = stackalloc byte[32]; + const string expected = "6ca13d52ca70c883e0f0bb101e425a89e8624de51db2d2392593af6a84118090"; + + // When + bool result = algorithm.TryComputeHash(data, destination, 0, data.Length, 1, out int bytesWritten); + string actual = Convert.ToHexString(destination).ToLower(); + + // Then + Assert.True(result); + Assert.Equal(32, bytesWritten); + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "HashAlgorithm.TryComputeHash should return false for insufficient destination length")] + public void HashAlgorithmTryComputeHashShouldReturnFalseForInsufficientDestinationLength() + { + // Given + using HashAlgorithm algorithm = SHA256.Create(); + byte[] data = "abc123".ToByteArray(); + Span destination = stackalloc byte[16]; // Insufficient length + + // When + bool result = algorithm.TryComputeHash(data, destination, 0, data.Length, 1, out int bytesWritten); + + // Then + Assert.False(result); + Assert.Equal(0, bytesWritten); + } } diff --git a/OnixLabs.Security.Cryptography.UnitTests/HashTests.cs b/OnixLabs.Security.Cryptography.UnitTests/HashTests.cs index 3477422..1713ae9 100644 --- a/OnixLabs.Security.Cryptography.UnitTests/HashTests.cs +++ b/OnixLabs.Security.Cryptography.UnitTests/HashTests.cs @@ -15,7 +15,6 @@ using System; using System.IO; using System.Security.Cryptography; -using System.Threading.Tasks; using OnixLabs.Core; using Xunit; @@ -374,62 +373,6 @@ public void HashComputeShouldProduceTheExpectedHashUsingAStreamAndTwoRounds(stri Assert.Equal(expected, actual); } - [Theory(DisplayName = "Hash.ComputeAsync should produce the expected hash using a stream")] - [InlineData("abc123", "MD5", "e99a18c428cb38d5f260853678922e03")] - [InlineData("abc123", "SHA1", "6367c48dd193d56ea7b0baad25b19455e529f5ee")] - [InlineData("abc123", "SHA256", "6ca13d52ca70c883e0f0bb101e425a89e8624de51db2d2392593af6a84118090")] - [InlineData("abc123", "SHA384", "a31d79891919cad24f3264479d76884f581bee32e86778373db3a124de975dd86a40fc7f399b331133b281ab4b11a6ca")] - [InlineData("abc123", "SHA512", "c70b5dd9ebfb6f51d09d4132b7170c9d20750a7852f00680f65658f0310e810056e6763c34c9a00b0e940076f54495c169fc2302cceb312039271c43469507dc")] - public async Task HashComputeAsyncShouldProduceTheExpectedHashUsingAStream(string data, string algorithmName, string expected) - { - // Given - Stream stream = new MemoryStream(data.ToByteArray()); - HashAlgorithm algorithm = algorithmName switch - { - "MD5" => MD5.Create(), - "SHA1" => SHA1.Create(), - "SHA256" => SHA256.Create(), - "SHA384" => SHA384.Create(), - "SHA512" => SHA512.Create(), - _ => throw new ArgumentException($"Unknown hash algorithm name: {algorithmName}.") - }; - - // When - Hash candidate = await Hash.ComputeAsync(algorithm, stream); - string actual = candidate.ToString(); - - // Then - Assert.Equal(expected, actual); - } - - [Theory(DisplayName = "Hash.ComputeAsync should produce the expected hash using a stream and two rounds")] - [InlineData("abc123", "MD5", "b106dc6352e5ec1f8aafd8c406d34d92")] - [InlineData("abc123", "SHA1", "6691484ea6b50ddde1926a220da01fa9e575c18a")] - [InlineData("abc123", "SHA256", "efaaeb3b1d1d85e8587ef0527ca43b9575ce8149ba1ee41583d3d19bd130daf8")] - [InlineData("abc123", "SHA384", "d58e9a112b8c637df5d2e33af03ce738dd1c57657243d70d2fa8f76a99fa9a0e2f4abf50d9a88e8958f2d5f6fa002190")] - [InlineData("abc123", "SHA512", "c2c9d705d7a1ed34247649bbe64c6edd2035e0a4c9ae1c063170f5ee2aeca09125cc0a8b30593c07a18801d6e0570de22e8dc40a59bc1f59a49834c05ed49949")] - public async Task HashComputeAsyncShouldProduceTheExpectedHashUsingAStreamAndTwoRounds(string data, string algorithmName, string expected) - { - // Given - Stream stream = new MemoryStream(data.ToByteArray()); - HashAlgorithm algorithm = algorithmName switch - { - "MD5" => MD5.Create(), - "SHA1" => SHA1.Create(), - "SHA256" => SHA256.Create(), - "SHA384" => SHA384.Create(), - "SHA512" => SHA512.Create(), - _ => throw new ArgumentException($"Unknown hash algorithm name: {algorithmName}.") - }; - - // When - Hash candidate = await Hash.ComputeAsync(algorithm, stream, 2); - string actual = candidate.ToString(); - - // Then - Assert.Equal(expected, actual); - } - [Fact(DisplayName = "Hash.ToNamedHash should produce the expected result")] public void HashToNamedHashShouldProduceExpectedResult() { diff --git a/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeGenericTests.cs b/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeGenericTests.cs index 8f9447b..0fdcfa0 100644 --- a/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeGenericTests.cs +++ b/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeGenericTests.cs @@ -56,6 +56,7 @@ public void IdenticalMerkleTreesShouldBeConsideredEqual() Assert.True(a.Equals(b)); Assert.True(a == b); Assert.False(a != b); + Assert.True(a.GetHashCode() == b.GetHashCode()); } [Fact(DisplayName = "Different Merkle trees should not be considered equal")] @@ -71,6 +72,7 @@ public void DifferentMerkleTreesShouldNotBeConsideredEqual() Assert.False(a.Equals(b)); Assert.False(a == b); Assert.True(a != b); + Assert.True(a.GetHashCode() != b.GetHashCode()); } [Fact(DisplayName = "MerkleTree.GetLeafHashes should produce the same leaf hashes that the tree was constructed with")] diff --git a/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeTests.cs b/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeTests.cs index 4f7bc20..9ab5ec8 100644 --- a/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeTests.cs +++ b/OnixLabs.Security.Cryptography.UnitTests/MerkleTreeTests.cs @@ -39,6 +39,7 @@ public void IdenticalMerkleTreesShouldBeConsideredEqual() Assert.True(a.Equals(b)); Assert.True(a == b); Assert.False(a != b); + Assert.True(a.GetHashCode() == b.GetHashCode()); } [Fact(DisplayName = "Different Merkle trees should not be considered equal")] @@ -54,6 +55,7 @@ public void DifferentMerkleTreesShouldNotBeConsideredEqual() Assert.False(a.Equals(b)); Assert.False(a == b); Assert.True(a != b); + Assert.True(a.GetHashCode() != b.GetHashCode()); } [Fact(DisplayName = "MerkleTree.GetLeafHashes should produce the same leaf hashes that the tree was constructed with")] diff --git a/OnixLabs.Security.Cryptography.UnitTests/PrivateKeyTests.cs b/OnixLabs.Security.Cryptography.UnitTests/PrivateKeyTests.cs index be25240..0987f69 100644 --- a/OnixLabs.Security.Cryptography.UnitTests/PrivateKeyTests.cs +++ b/OnixLabs.Security.Cryptography.UnitTests/PrivateKeyTests.cs @@ -121,4 +121,18 @@ public void DifferentPrivateKeyValuesShouldProduceDifferentPrivateKeyCodes() // Then Assert.NotEqual(leftHashCode, rightHashCode); } + + [Fact(DisplayName = "PrivateKey.ToNamedPrivateKey should produce the expected result")] + public void PrivateKeyToNamedPrivateKeyShouldProduceExpectedResult() + { + // Given + PrivateKey privateKey = new TestPrivateKey([1, 2, 3, 4]); + + // When + NamedPrivateKey namedPrivateKey = privateKey.ToNamedPrivateKey(); + + // Then + Assert.Equal("TEST", namedPrivateKey.AlgorithmName); + Assert.Equal(privateKey, namedPrivateKey.PrivateKey); + } } diff --git a/OnixLabs.Security.Cryptography.UnitTests/SecretTests.cs b/OnixLabs.Security.Cryptography.UnitTests/SecretTests.cs index 905d6b8..fe3c54c 100644 --- a/OnixLabs.Security.Cryptography.UnitTests/SecretTests.cs +++ b/OnixLabs.Security.Cryptography.UnitTests/SecretTests.cs @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; +using System.Buffers; +using System.Text; +using OnixLabs.Core; +using OnixLabs.Core.Text; using Xunit; namespace OnixLabs.Security.Cryptography.UnitTests; @@ -53,7 +58,7 @@ public void SecretValueShouldNotBeModifiedWhenAlteringTheOriginalByteArray() public void SecretValueShouldNotBeModifiedWhenAlteringTheObtainedByteArray() { // Given - Secret candidate = new([1, 2, 3, 4]); + Secret candidate = new byte[] { 1, 2, 3, 4 }; const string expected = "01020304"; // When @@ -69,8 +74,8 @@ public void SecretValueShouldNotBeModifiedWhenAlteringTheObtainedByteArray() public void IdenticalSecretValuesShouldBeConsideredEqual() { // Given - Secret left = new([1, 2, 3, 4]); - Secret right = new([1, 2, 3, 4]); + Secret left = new byte[] { 1, 2, 3, 4 }; + Secret right = new byte[] { 1, 2, 3, 4 }; // Then Assert.Equal(left, right); @@ -82,8 +87,8 @@ public void IdenticalSecretValuesShouldBeConsideredEqual() public void DifferentSecretValuesShouldNotBeConsideredEqual() { // Given - Secret left = new([1, 2, 3, 4]); - Secret right = new([5, 6, 7, 8]); + Secret left = new byte[] { 1, 2, 3, 4 }; + Secret right = new byte[] { 5, 6, 7, 8 }; // Then Assert.NotEqual(left, right); @@ -95,8 +100,8 @@ public void DifferentSecretValuesShouldNotBeConsideredEqual() public void IdenticalSecretValuesShouldProduceIdenticalSecretCodes() { // Given - Secret left = new([1, 2, 3, 4]); - Secret right = new([1, 2, 3, 4]); + Secret left = new byte[] { 1, 2, 3, 4 }; + Secret right = new byte[] { 1, 2, 3, 4 }; // When int leftHashCode = left.GetHashCode(); @@ -110,8 +115,8 @@ public void IdenticalSecretValuesShouldProduceIdenticalSecretCodes() public void DifferentSecretValuesShouldProduceDifferentSecretCodes() { // Given - Secret left = new([1, 2, 3, 4]); - Secret right = new([5, 6, 7, 8]); + Secret left = new byte[] { 1, 2, 3, 4 }; + Secret right = new byte[] { 5, 6, 7, 8 }; // When int leftHashCode = left.GetHashCode(); @@ -120,4 +125,132 @@ public void DifferentSecretValuesShouldProduceDifferentSecretCodes() // Then Assert.NotEqual(leftHashCode, rightHashCode); } + + [Fact(DisplayName = "Secret should be constructable from a string")] + public void SecretShouldBeConstructableFromString() + { + // Given + const string value = "test"; + Secret candidate = value; + byte[] expected = Encoding.UTF8.GetBytes(value); + + // When + byte[] actual = candidate.ToByteArray(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Secret should be constructable from a char array")] + public void SecretShouldBeConstructableFromCharArray() + { + // Given + char[] value = ['t', 'e', 's', 't']; + Secret candidate = value; + byte[] expected = Encoding.UTF8.GetBytes(value); + + // When + byte[] actual = candidate.ToByteArray(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Secret should be constructable from a ReadOnlySequence")] + public void SecretShouldBeConstructableFromReadOnlySequence() + { + // Given + ReadOnlySequence value = new ReadOnlySequence(new char[] { 't', 'e', 's', 't' }); + Secret candidate = value; + byte[] expected = Encoding.UTF8.GetBytes(value.ToArray()); + + // When + byte[] actual = candidate.ToByteArray(); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Secret should parse a valid string correctly")] + public void SecretShouldParseValidString() + { + // Given + string value = "01020304"; + Secret expected = new byte[] { 1, 2, 3, 4 }; + + // When + Secret actual = Secret.Parse(value); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "Secret should throw on invalid string parse")] + public void SecretShouldThrowOnInvalidStringParse() + { + // Given + string value = "invalid"; + + // Then + Assert.Throws(() => Secret.Parse(value)); + } + + [Fact(DisplayName = "TryParse should return true for valid string and output correct Secret")] + public void TryParseShouldReturnTrueForValidString() + { + // Given + string value = "01020304"; + Secret expected = new byte[] { 1, 2, 3, 4 }; + + // When + bool success = Secret.TryParse(value, null, out Secret actual); + + // Then + Assert.True(success); + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "TryParse should return false for invalid string and output default Secret")] + public void TryParseShouldReturnFalseForInvalidString() + { + // Given + string value = "invalid"; + + // When + bool success = Secret.TryParse(value, null, out Secret actual); + + // Then + Assert.False(success); + Assert.Equal(default, actual); + } + + [Fact(DisplayName = "ToString should return correct string representation with provided encoding")] + public void ToStringShouldReturnCorrectStringWithEncoding() + { + // Given + byte[] value = "ABCxyz123".ToByteArray(); + Secret candidate = new(value); + const string expected = "ABCxyz123"; + + // When + string actual = candidate.ToString(Encoding.UTF8); + + // Then + Assert.Equal(expected, actual); + } + + [Fact(DisplayName = "ToString should return correct string representation with provided format provider")] + public void ToStringShouldReturnCorrectStringWithFormatProvider() + { + // Given + byte[] value = [1, 2, 3, 4]; + Secret candidate = new(value); + string expected = "01020304"; + + // When + string actual = candidate.ToString(Base16FormatProvider.Invariant); + + // Then + Assert.Equal(expected, actual); + } } diff --git a/OnixLabs.Security.Cryptography/DigitalSignature.Equatable.cs b/OnixLabs.Security.Cryptography/DigitalSignature.Equatable.cs index 02cd26c..171188a 100644 --- a/OnixLabs.Security.Cryptography/DigitalSignature.Equatable.cs +++ b/OnixLabs.Security.Cryptography/DigitalSignature.Equatable.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Linq; +using System.Collections.Generic; using OnixLabs.Core.Linq; namespace OnixLabs.Security.Cryptography; @@ -45,7 +45,7 @@ public readonly partial struct DigitalSignature /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(DigitalSignature left, DigitalSignature right) => Equals(left, right); + public static bool operator ==(DigitalSignature left, DigitalSignature right) => EqualityComparer.Default.Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -53,5 +53,5 @@ public readonly partial struct DigitalSignature /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(DigitalSignature left, DigitalSignature right) => !Equals(left, right); + public static bool operator !=(DigitalSignature left, DigitalSignature right) => !EqualityComparer.Default.Equals(left, right); } diff --git a/OnixLabs.Security.Cryptography/DigitalSignature.To.cs b/OnixLabs.Security.Cryptography/DigitalSignature.To.cs index d9fb5ad..50b7bca 100644 --- a/OnixLabs.Security.Cryptography/DigitalSignature.To.cs +++ b/OnixLabs.Security.Cryptography/DigitalSignature.To.cs @@ -13,7 +13,6 @@ // limitations under the License. using System; -using System.Text; using OnixLabs.Core; using OnixLabs.Core.Text; @@ -27,6 +26,12 @@ public readonly partial struct DigitalSignature /// Return the underlying representation of the current instance. public byte[] ToByteArray() => value.Copy(); + /// + /// Gets the underlying representation of the current instance as a new instance. + /// + /// Return the underlying representation of the current instance as a new instance. + public ReadOnlySpan ToReadOnlySpan() => value; + /// /// Returns a that represents the current object. /// diff --git a/OnixLabs.Security.Cryptography/DigitalSignature.cs b/OnixLabs.Security.Cryptography/DigitalSignature.cs index e4a2007..fa5d25d 100644 --- a/OnixLabs.Security.Cryptography/DigitalSignature.cs +++ b/OnixLabs.Security.Cryptography/DigitalSignature.cs @@ -22,12 +22,13 @@ namespace OnixLabs.Security.Cryptography; /// Represents a cryptographic digital signature. /// /// The underlying value of the cryptographic digital signature. -public readonly partial struct DigitalSignature(ReadOnlySpan value) : ICryptoPrimitive, ISpanParsable +public readonly partial struct DigitalSignature(ReadOnlySpan value) : ICryptoPrimitive, ISpanParsable, ISpanBinaryConvertible { /// /// Initializes a new instance of the struct. /// /// The with which to initialize the instance. + // ReSharper disable once MemberCanBePrivate.Global public DigitalSignature(ReadOnlySequence value) : this(ReadOnlySpan.Empty) => value.CopyTo(out this.value); private readonly byte[] value = value.ToArray(); diff --git a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Convertible.cs b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Convertible.cs index 62929a0..3adad3c 100644 --- a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Convertible.cs +++ b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Convertible.cs @@ -17,6 +17,7 @@ namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed partial class EcdhPrivateKey { /// diff --git a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Create.cs b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Create.cs index 4058af6..eaacec6 100644 --- a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Create.cs +++ b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Create.cs @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Security.Cryptography; namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed partial class EcdhPrivateKey { /// diff --git a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Get.cs b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Get.cs index 65f78b5..2629552 100644 --- a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Get.cs +++ b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Get.cs @@ -26,6 +26,7 @@ public EcdhPublicKey GetPublicKey() { using ECDiffieHellman key = ImportKeyData(); byte[] keyData = key.ExportSubjectPublicKeyInfo(); + // ReSharper disable once HeapView.ObjectAllocation.Evident return new EcdhPublicKey(keyData); } } diff --git a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Import.cs b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Import.cs index fd98614..a40b166 100644 --- a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Import.cs +++ b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Import.cs @@ -18,6 +18,7 @@ namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed partial class EcdhPrivateKey { /// diff --git a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Parse.cs b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Parse.cs index d72e610..ad0c88d 100644 --- a/OnixLabs.Security.Cryptography/EcdhPrivateKey.Parse.cs +++ b/OnixLabs.Security.Cryptography/EcdhPrivateKey.Parse.cs @@ -64,6 +64,7 @@ public static EcdhPrivateKey Parse(ReadOnlySpan value, IFormatProvider? pr public static bool TryParse(ReadOnlySpan value, IFormatProvider? provider, out EcdhPrivateKey result) { bool isDecoded = IBaseCodec.TryGetBytes(value, provider ?? Base16FormatProvider.Invariant, out byte[] bytes); + // ReSharper disable once HeapView.ObjectAllocation.Evident result = new EcdhPrivateKey(bytes); return isDecoded; } diff --git a/OnixLabs.Security.Cryptography/EcdhPrivateKey.cs b/OnixLabs.Security.Cryptography/EcdhPrivateKey.cs index f381771..2393284 100644 --- a/OnixLabs.Security.Cryptography/EcdhPrivateKey.cs +++ b/OnixLabs.Security.Cryptography/EcdhPrivateKey.cs @@ -27,6 +27,7 @@ public sealed partial class EcdhPrivateKey(ReadOnlySpan keyData) : Private /// Initializes a new instance of the struct. /// /// The with which to initialize the instance. + // ReSharper disable once MemberCanBePrivate.Global public EcdhPrivateKey(ReadOnlySequence value) : this(value.ToArray()) { } diff --git a/OnixLabs.Security.Cryptography/EcdhPublicKey.Convertible.cs b/OnixLabs.Security.Cryptography/EcdhPublicKey.Convertible.cs index 531100e..ec117c0 100644 --- a/OnixLabs.Security.Cryptography/EcdhPublicKey.Convertible.cs +++ b/OnixLabs.Security.Cryptography/EcdhPublicKey.Convertible.cs @@ -17,6 +17,7 @@ namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed partial class EcdhPublicKey { /// diff --git a/OnixLabs.Security.Cryptography/EcdhPublicKey.Parse.cs b/OnixLabs.Security.Cryptography/EcdhPublicKey.Parse.cs index c16da17..a6aa47d 100644 --- a/OnixLabs.Security.Cryptography/EcdhPublicKey.Parse.cs +++ b/OnixLabs.Security.Cryptography/EcdhPublicKey.Parse.cs @@ -64,6 +64,7 @@ public static EcdhPublicKey Parse(ReadOnlySpan value, IFormatProvider? pro public static bool TryParse(ReadOnlySpan value, IFormatProvider? provider, out EcdhPublicKey result) { bool isDecoded = IBaseCodec.TryGetBytes(value, provider ?? Base16FormatProvider.Invariant, out byte[] bytes); + // ReSharper disable once HeapView.ObjectAllocation.Evident result = new EcdhPublicKey(bytes); return isDecoded; } diff --git a/OnixLabs.Security.Cryptography/EcdhPublicKey.cs b/OnixLabs.Security.Cryptography/EcdhPublicKey.cs index 2a2e3bc..2982b10 100644 --- a/OnixLabs.Security.Cryptography/EcdhPublicKey.cs +++ b/OnixLabs.Security.Cryptography/EcdhPublicKey.cs @@ -27,6 +27,7 @@ public sealed partial class EcdhPublicKey(ReadOnlySpan keyData) : PublicKe /// Initializes a new instance of the struct. /// /// The with which to initialize the instance. + // ReSharper disable once MemberCanBePrivate.Global public EcdhPublicKey(ReadOnlySequence value) : this(value.ToArray()) { } diff --git a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Convertible.cs b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Convertible.cs index 677193a..79ccd7c 100644 --- a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Convertible.cs +++ b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Convertible.cs @@ -17,6 +17,7 @@ namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed partial class EcdsaPrivateKey { /// diff --git a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Create.cs b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Create.cs index 532c2fc..a4d55af 100644 --- a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Create.cs +++ b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Create.cs @@ -16,6 +16,7 @@ namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed partial class EcdsaPrivateKey { /// diff --git a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Get.cs b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Get.cs index 21ac7d1..800c2cd 100644 --- a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Get.cs +++ b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Get.cs @@ -26,6 +26,7 @@ public EcdsaPublicKey GetPublicKey() { using ECDsa key = ImportKeyData(); byte[] keyData = key.ExportSubjectPublicKeyInfo(); + // ReSharper disable once HeapView.ObjectAllocation.Evident return new EcdsaPublicKey(keyData); } } diff --git a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Import.cs b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Import.cs index 5cc0ea6..6f3a80e 100644 --- a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Import.cs +++ b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Import.cs @@ -18,6 +18,7 @@ namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed partial class EcdsaPrivateKey { /// diff --git a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Parse.cs b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Parse.cs index 8dd5d5e..3600ab8 100644 --- a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Parse.cs +++ b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.Parse.cs @@ -64,6 +64,7 @@ public static EcdsaPrivateKey Parse(ReadOnlySpan value, IFormatProvider? p public static bool TryParse(ReadOnlySpan value, IFormatProvider? provider, out EcdsaPrivateKey result) { bool isDecoded = IBaseCodec.TryGetBytes(value, provider ?? Base16FormatProvider.Invariant, out byte[] bytes); + // ReSharper disable once HeapView.ObjectAllocation.Evident result = new EcdsaPrivateKey(bytes); return isDecoded; } diff --git a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.cs b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.cs index fc15739..4cc5fa5 100644 --- a/OnixLabs.Security.Cryptography/EcdsaPrivateKey.cs +++ b/OnixLabs.Security.Cryptography/EcdsaPrivateKey.cs @@ -27,6 +27,7 @@ public sealed partial class EcdsaPrivateKey(ReadOnlySpan keyData) : Privat /// Initializes a new instance of the struct. /// /// The with which to initialize the instance. + // ReSharper disable once MemberCanBePrivate.Global public EcdsaPrivateKey(ReadOnlySequence value) : this(value.ToArray()) { } diff --git a/OnixLabs.Security.Cryptography/EcdsaPublicKey.Convertible.cs b/OnixLabs.Security.Cryptography/EcdsaPublicKey.Convertible.cs index b60b98a..6b86a7c 100644 --- a/OnixLabs.Security.Cryptography/EcdsaPublicKey.Convertible.cs +++ b/OnixLabs.Security.Cryptography/EcdsaPublicKey.Convertible.cs @@ -17,6 +17,7 @@ namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed partial class EcdsaPublicKey { /// diff --git a/OnixLabs.Security.Cryptography/EcdsaPublicKey.Parse.cs b/OnixLabs.Security.Cryptography/EcdsaPublicKey.Parse.cs index 2ef0ac1..a3f7f4d 100644 --- a/OnixLabs.Security.Cryptography/EcdsaPublicKey.Parse.cs +++ b/OnixLabs.Security.Cryptography/EcdsaPublicKey.Parse.cs @@ -64,6 +64,7 @@ public static EcdsaPublicKey Parse(ReadOnlySpan value, IFormatProvider? pr public static bool TryParse(ReadOnlySpan value, IFormatProvider? provider, out EcdsaPublicKey result) { bool isDecoded = IBaseCodec.TryGetBytes(value, provider ?? Base16FormatProvider.Invariant, out byte[] bytes); + // ReSharper disable once HeapView.ObjectAllocation.Evident result = new EcdsaPublicKey(bytes); return isDecoded; } diff --git a/OnixLabs.Security.Cryptography/EcdsaPublicKey.cs b/OnixLabs.Security.Cryptography/EcdsaPublicKey.cs index 706bd5f..b2233f6 100644 --- a/OnixLabs.Security.Cryptography/EcdsaPublicKey.cs +++ b/OnixLabs.Security.Cryptography/EcdsaPublicKey.cs @@ -27,6 +27,7 @@ public sealed partial class EcdsaPublicKey(ReadOnlySpan keyData) : PublicK /// Initializes a new instance of the struct. /// /// The with which to initialize the instance. + // ReSharper disable once MemberCanBePrivate.Global public EcdsaPublicKey(ReadOnlySequence value) : this(value.ToArray()) { } diff --git a/OnixLabs.Security.Cryptography/Extensions.HashAlgorithm.cs b/OnixLabs.Security.Cryptography/Extensions.HashAlgorithm.cs index bbd121c..d3172d5 100644 --- a/OnixLabs.Security.Cryptography/Extensions.HashAlgorithm.cs +++ b/OnixLabs.Security.Cryptography/Extensions.HashAlgorithm.cs @@ -17,8 +17,6 @@ using System.IO; using System.Security.Cryptography; using System.Text; -using System.Threading; -using System.Threading.Tasks; using OnixLabs.Core; using OnixLabs.Core.Text; @@ -30,90 +28,162 @@ namespace OnixLabs.Security.Cryptography; [EditorBrowsable(EditorBrowsableState.Never)] public static class HashAlgorithmExtensions { + private const string ComputeHashFailed = "Failed to compute a hash for the specified input data."; + /// - /// Computes the hash value for the specified byte array. + /// Computes the hash value for the specified value. /// /// The which will be used to compute a hash value. /// The input data to compute the hash for. /// The number of rounds that the input data should be hashed. /// Returns the computed hash value. - public static byte[] ComputeHash(this HashAlgorithm algorithm, IBinaryConvertible data, int rounds = 1) => - algorithm.ComputeHash(new MemoryStream(data.ToByteArray()), rounds); + /// If the hash could not be computed for the specified input data. + public static byte[] ComputeHash(this HashAlgorithm algorithm, ReadOnlySpan data, int rounds = 1) + { + Span destination = stackalloc byte[algorithm.HashSize / 8]; + + if (!algorithm.TryComputeHash(data, destination, 0, data.Length, rounds, out int _)) + throw new CryptographicException(ComputeHashFailed); + + return destination.ToArray(); + } /// - /// Computes the hash value for the specified byte array. + /// Computes the hash value for the specified value. /// /// The which will be used to compute a hash value. /// The input data to compute the hash for. + /// The offset into the input from which to begin using data. + /// The number of bytes in the input to use as data. /// The number of rounds that the input data should be hashed. /// Returns the computed hash value. - public static byte[] ComputeHash(this HashAlgorithm algorithm, byte[] data, int rounds) => - algorithm.ComputeHash(new MemoryStream(data), rounds); + /// If the hash could not be computed for the specified input data. + public static byte[] ComputeHash(this HashAlgorithm algorithm, ReadOnlySpan data, int offset, int count, int rounds = 1) + { + ReadOnlySpan source = data.Slice(offset, count); + Span destination = stackalloc byte[algorithm.HashSize / 8]; + + if (!algorithm.TryComputeHash(source, destination, 0, source.Length, rounds, out int _)) + throw new CryptographicException(ComputeHashFailed); + + return destination.ToArray(); + } /// - /// Computes the hash value for the specified byte array. + /// Computes the hash value for the specified . /// /// The which will be used to compute a hash value. /// The input data to compute the hash for. - /// The offset into the byte array from which to begin using data. - /// The number of bytes in the array to use as data. + /// The which will be used to convert the specified . /// The number of rounds that the input data should be hashed. /// Returns the computed hash value. - public static byte[] ComputeHash(this HashAlgorithm algorithm, byte[] data, int offset, int count, int rounds) => - algorithm.ComputeHash(data.Copy(offset, count), rounds); + /// If the hash could not be computed for the specified input data. + public static byte[] ComputeHash(this HashAlgorithm algorithm, ReadOnlySpan data, Encoding? encoding = null, int rounds = 1) => + algorithm.ComputeHash(encoding.GetOrDefault().GetBytes(data.ToArray()), rounds); /// - /// Computes the hash value for the specified . + /// Computes the hash value for the specified value. /// /// The which will be used to compute a hash value. /// The input data to compute the hash for. - /// The which will be used to convert the specified . /// The number of rounds that the input data should be hashed. /// Returns the computed hash value. - public static byte[] ComputeHash(this HashAlgorithm algorithm, ReadOnlySpan data, Encoding? encoding = null, int rounds = 1) => - algorithm.ComputeHash(encoding.GetOrDefault().GetBytes(data.ToArray()), rounds); + /// If the hash could not be computed for the specified input data. + public static byte[] ComputeHash(this HashAlgorithm algorithm, IBinaryConvertible data, int rounds = 1) => + algorithm.ComputeHash(data.ToByteArray(), rounds); /// - /// Computes the hash value for the specified object. + /// Computes the hash value for the specified value. /// /// The which will be used to compute a hash value. - /// The input data to compute the hash for. + /// The input data to compute the hash for. + /// The offset into the input from which to begin using data. + /// The number of bytes in the input to use as data. /// The number of rounds that the input data should be hashed. /// Returns the computed hash value. - public static byte[] ComputeHash(this HashAlgorithm algorithm, Stream stream, int rounds) - { - Require(rounds > 0, "Rounds must be greater than zero", nameof(rounds)); + /// If the hash could not be computed for the specified input data. + public static byte[] ComputeHash(this HashAlgorithm algorithm, IBinaryConvertible data, int offset, int count, int rounds = 1) => + algorithm.ComputeHash(data.ToByteArray(), offset, count, rounds); - byte[] data = algorithm.ComputeHash(stream); - while (--rounds > 0) data = algorithm.ComputeHash(data); - return data; - } + /// + /// Computes the hash value for the specified value. + /// + /// The which will be used to compute a hash value. + /// The input data to compute the hash for. + /// The number of rounds that the input data should be hashed. + /// Returns the computed hash value. + /// If the hash could not be computed for the specified input data. + public static byte[] ComputeHash(this HashAlgorithm algorithm, ISpanBinaryConvertible data, int rounds = 1) => + algorithm.ComputeHash(data.ToReadOnlySpan(), rounds); /// - /// Asynchronously computes the hash value for the specified object. + /// Computes the hash value for the specified value. /// /// The which will be used to compute a hash value. /// The input data to compute the hash for. + /// The offset into the input from which to begin using data. + /// The number of bytes in the input to use as data. /// The number of rounds that the input data should be hashed. - /// The token to monitor for cancellation requests. - /// Returns a task that represents the asynchronous compute hash operation and wraps the computed hash value. - public static async Task ComputeHashAsync(this HashAlgorithm algorithm, IBinaryConvertible data, int rounds = 1, CancellationToken token = default) => - await algorithm.ComputeHashAsync(new MemoryStream(data.ToByteArray()), rounds, token).ConfigureAwait(false); + /// Returns the computed hash value. + /// If the hash could not be computed for the specified input data. + public static byte[] ComputeHash(this HashAlgorithm algorithm, ISpanBinaryConvertible data, int offset, int count, int rounds = 1) => + algorithm.ComputeHash(data.ToReadOnlySpan(), offset, count, rounds); /// - /// Asynchronously computes the hash value for the specified object. + /// Computes the hash value for the specified value. /// /// The which will be used to compute a hash value. /// The input data to compute the hash for. /// The number of rounds that the input data should be hashed. - /// The token to monitor for cancellation requests. - /// Returns a task that represents the asynchronous compute hash operation and wraps the computed hash value. - public static async Task ComputeHashAsync(this HashAlgorithm algorithm, Stream stream, int rounds, CancellationToken token = default) + /// Returns the computed hash value. + /// If the hash could not be computed for the specified input data. + public static byte[] ComputeHash(this HashAlgorithm algorithm, Stream stream, int rounds) => rounds is 1 + ? algorithm.ComputeHash(stream) + : algorithm.ComputeHash(algorithm.ComputeHash(stream), rounds - 1); + + /// + /// Attempts to compute the hash value for the specified . + /// + /// The which will be used to compute a hash value. + /// The input to compute the hash code for. + /// The buffer to receive the hash value. + /// The offset into the input from which to begin using data. + /// The number of bytes in the input to use as data. + /// The number of rounds that the input data should be hashed. + /// When this method returns, the total number of bytes written into . This parameter is treated as uninitialized. + /// Returns if is long enough to receive the hash value; otherwise, . + // ReSharper disable once MemberCanBePrivate.Global + public static bool TryComputeHash(this HashAlgorithm algorithm, ReadOnlySpan source, Span destination, int offset, int count, int rounds, out int bytesWritten) { - Require(rounds > 0, "Rounds must be greater than zero", nameof(rounds)); + try + { + if (rounds < 1 || destination.Length < algorithm.HashSize / 8) + { + bytesWritten = default; + return false; + } + + source = source.Slice(offset, count); + int result = default; + + while (rounds-- > 0) + { + if (!algorithm.TryComputeHash(source, destination, out result)) + { + bytesWritten = default; + return false; + } + + source = destination; + } - MemoryStream memoryStream = new(await algorithm.ComputeHashAsync(stream, token).ConfigureAwait(false)); - while (--rounds > 0) memoryStream = new MemoryStream(await algorithm.ComputeHashAsync(memoryStream, token).ConfigureAwait(false)); - return memoryStream.ToArray(); + bytesWritten = result; + return true; + } + catch + { + bytesWritten = default; + return false; + } } } diff --git a/OnixLabs.Security.Cryptography/Hash.Compute.cs b/OnixLabs.Security.Cryptography/Hash.Compute.cs index 40f46ad..cfabe04 100644 --- a/OnixLabs.Security.Cryptography/Hash.Compute.cs +++ b/OnixLabs.Security.Cryptography/Hash.Compute.cs @@ -16,23 +16,12 @@ using System.IO; using System.Security.Cryptography; using System.Text; -using System.Threading; -using System.Threading.Tasks; using OnixLabs.Core; namespace OnixLabs.Security.Cryptography; public readonly partial struct Hash { - /// - /// Computes the hash of the specified data, using the specified . - /// - /// The which will be used to compute the hash. - /// The data from which to compute a hash. - /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, byte[] data) => - algorithm.ComputeHash(data); - /// /// Computes the hash of the specified data, using the specified . /// @@ -40,20 +29,9 @@ public static Hash Compute(HashAlgorithm algorithm, byte[] data) => /// The data from which to compute a hash. /// The number of rounds that the input data should be hashed. /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, byte[] data, int rounds) => + public static Hash Compute(HashAlgorithm algorithm, byte[] data, int rounds = 1) => algorithm.ComputeHash(data, rounds); - /// - /// Computes the hash of the specified data, using the specified . - /// - /// The which will be used to compute the hash. - /// The data from which to compute a hash. - /// The offset into the from which to begin using data. - /// The number of bytes in the array to use as data. - /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, byte[] data, int offset, int count) => - algorithm.ComputeHash(data, offset, count); - /// /// Computes the hash of the specified data, using the specified . /// @@ -63,18 +41,9 @@ public static Hash Compute(HashAlgorithm algorithm, byte[] data, int offset, int /// The number of bytes in the array to use as data. /// The number of rounds that the input data should be hashed. /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, byte[] data, int offset, int count, int rounds) => + public static Hash Compute(HashAlgorithm algorithm, byte[] data, int offset, int count, int rounds = 1) => algorithm.ComputeHash(data, offset, count, rounds); - /// - /// Computes the hash of the specified , using the specified . - /// - /// The which will be used to compute the hash. - /// The data from which to compute a hash. - /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, Stream stream) => - algorithm.ComputeHash(stream); - /// /// Computes the hash of the specified , using the specified . /// @@ -82,7 +51,7 @@ public static Hash Compute(HashAlgorithm algorithm, Stream stream) => /// The data from which to compute a hash. /// The number of rounds that the input data should be hashed. /// Returns a cryptographic hash of the specified data. - public static Hash Compute(HashAlgorithm algorithm, Stream stream, int rounds) => + public static Hash Compute(HashAlgorithm algorithm, Stream stream, int rounds = 1) => algorithm.ComputeHash(stream, rounds); /// @@ -105,36 +74,4 @@ public static Hash Compute(HashAlgorithm algorithm, IBinaryConvertible data, int /// Returns a cryptographic hash of the specified data. public static Hash Compute(HashAlgorithm algorithm, ReadOnlySpan data, Encoding? encoding = null, int rounds = 1) => algorithm.ComputeHash(data, encoding, rounds); - - /// - /// Asynchronously computes the hash of the specified data, using the specified . - /// - /// The which will be used to compute the hash. - /// The data from which to compute a hash. - /// The token to monitor for cancellation requests. - /// Returns a cryptographic hash of the specified data. - public static async Task ComputeAsync(HashAlgorithm algorithm, Stream stream, CancellationToken token = default) => - await algorithm.ComputeHashAsync(stream, token).ConfigureAwait(false); - - /// - /// Asynchronously computes the hash of the specified data, using the specified . - /// - /// The which will be used to compute the hash. - /// The data from which to compute a hash. - /// The number of rounds that the input data should be hashed. - /// The token to monitor for cancellation requests. - /// Returns a cryptographic hash of the specified data. - public static async Task ComputeAsync(HashAlgorithm algorithm, Stream stream, int rounds, CancellationToken token = default) => - await algorithm.ComputeHashAsync(stream, rounds, token).ConfigureAwait(false); - - /// - /// Asynchronously computes the hash of the specified data, using the specified . - /// - /// The which will be used to compute the hash. - /// The input data to compute the hash for. - /// The number of rounds that the input data should be hashed. - /// The token to monitor for cancellation requests. - /// Returns a cryptographic hash of the specified data. - public static async Task ComputeAsync(HashAlgorithm algorithm, IBinaryConvertible data, int rounds = 1, CancellationToken token = default) => - await algorithm.ComputeHashAsync(data, rounds, token).ConfigureAwait(false); } diff --git a/OnixLabs.Security.Cryptography/Hash.Equatable.cs b/OnixLabs.Security.Cryptography/Hash.Equatable.cs index 41ac53d..e4ef0f9 100644 --- a/OnixLabs.Security.Cryptography/Hash.Equatable.cs +++ b/OnixLabs.Security.Cryptography/Hash.Equatable.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Linq; +using System.Collections.Generic; using OnixLabs.Core.Linq; namespace OnixLabs.Security.Cryptography; @@ -45,7 +45,7 @@ public readonly partial struct Hash /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Hash left, Hash right) => Equals(left, right); + public static bool operator ==(Hash left, Hash right) => EqualityComparer.Default.Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -53,5 +53,5 @@ public readonly partial struct Hash /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Hash left, Hash right) => !Equals(left, right); + public static bool operator !=(Hash left, Hash right) => !EqualityComparer.Default.Equals(left, right); } diff --git a/OnixLabs.Security.Cryptography/Hash.To.cs b/OnixLabs.Security.Cryptography/Hash.To.cs index 9367489..7556dcc 100644 --- a/OnixLabs.Security.Cryptography/Hash.To.cs +++ b/OnixLabs.Security.Cryptography/Hash.To.cs @@ -27,6 +27,12 @@ public readonly partial struct Hash /// Return the underlying representation of the current instance. public byte[] ToByteArray() => value.Copy(); + /// + /// Gets the underlying representation of the current instance as a new instance. + /// + /// Return the underlying representation of the current instance as a new instance. + public ReadOnlySpan ToReadOnlySpan() => value; + /// /// Creates a new from the current instance. /// diff --git a/OnixLabs.Security.Cryptography/Hash.cs b/OnixLabs.Security.Cryptography/Hash.cs index 59253ba..dbb38b1 100644 --- a/OnixLabs.Security.Cryptography/Hash.cs +++ b/OnixLabs.Security.Cryptography/Hash.cs @@ -23,12 +23,13 @@ namespace OnixLabs.Security.Cryptography; /// Represents a cryptographic hash. /// /// The underlying value of the cryptographic hash. -public readonly partial struct Hash(ReadOnlySpan value) : ICryptoPrimitive, IValueComparable, ISpanParsable +public readonly partial struct Hash(ReadOnlySpan value) : ICryptoPrimitive, IValueComparable, ISpanParsable, ISpanBinaryConvertible { /// /// Initializes a new instance of the struct. /// /// The with which to initialize the instance. + // ReSharper disable once MemberCanBePrivate.Global public Hash(ReadOnlySequence value) : this(ReadOnlySpan.Empty) => value.CopyTo(out this.value); /// diff --git a/OnixLabs.Security.Cryptography/MerkleTree.Create.cs b/OnixLabs.Security.Cryptography/MerkleTree.Create.cs index 7ce94b6..25825d9 100644 --- a/OnixLabs.Security.Cryptography/MerkleTree.Create.cs +++ b/OnixLabs.Security.Cryptography/MerkleTree.Create.cs @@ -29,7 +29,12 @@ public abstract partial class MerkleTree /// Returns a new node that represents the Merkle root. public static MerkleTree Create(IEnumerable leaves, HashAlgorithm algorithm) { - IReadOnlyList nodes = leaves.Select(leaf => new MerkleTreeLeafNode(leaf)).ToList(); + // ReSharper disable once HeapView.ObjectAllocation + List nodes = []; + + // ReSharper disable once LoopCanBeConvertedToQuery, HeapView.ObjectAllocation.Possible, HeapView.ObjectAllocation.Evident + foreach (Hash leaf in leaves) nodes.Add(new MerkleTreeLeafNode(leaf)); + Require(nodes.IsNotEmpty(), "Cannot construct a Merkle tree from an empty list.", nameof(leaves)); return BuildMerkleTree(nodes, algorithm); } @@ -53,14 +58,17 @@ private static MerkleTree BuildMerkleTree(IReadOnlyList nodes, HashA while (true) { if (nodes.IsSingle()) return nodes.Single(); + // ReSharper disable once HeapView.ObjectAllocation.Evident if (nodes.IsCountOdd()) nodes = nodes.Append(new MerkleTreeEmptyNode(algorithm)).ToArray(); + // ReSharper disable once HeapView.ObjectAllocation List mergedNodes = []; for (int index = 0; index < nodes.Count; index += 2) { MerkleTree left = nodes[index]; MerkleTree right = nodes[index + 1]; + // ReSharper disable once HeapView.ObjectAllocation.Evident mergedNodes.Add(new MerkleTreeBranchNode(left, right, algorithm)); } diff --git a/OnixLabs.Security.Cryptography/MerkleTree.Equatable.cs b/OnixLabs.Security.Cryptography/MerkleTree.Equatable.cs index f93d878..565212f 100644 --- a/OnixLabs.Security.Cryptography/MerkleTree.Equatable.cs +++ b/OnixLabs.Security.Cryptography/MerkleTree.Equatable.cs @@ -44,7 +44,7 @@ public abstract partial class MerkleTree /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(MerkleTree left, MerkleTree right) => Equals(left, right); + public static bool operator ==(MerkleTree? left, MerkleTree? right) => Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -52,5 +52,5 @@ public abstract partial class MerkleTree /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(MerkleTree left, MerkleTree right) => !Equals(left, right); + public static bool operator !=(MerkleTree? left, MerkleTree? right) => !Equals(left, right); } diff --git a/OnixLabs.Security.Cryptography/MerkleTree.Generic.Create.cs b/OnixLabs.Security.Cryptography/MerkleTree.Generic.Create.cs index d133d41..52aa750 100644 --- a/OnixLabs.Security.Cryptography/MerkleTree.Generic.Create.cs +++ b/OnixLabs.Security.Cryptography/MerkleTree.Generic.Create.cs @@ -29,7 +29,12 @@ public abstract partial class MerkleTree /// Returns a new node that represents the Merkle root. public static MerkleTree Create(IEnumerable leaves, HashAlgorithm algorithm) { - IReadOnlyList> nodes = leaves.Select(leaf => new MerkleTreeLeafNode(leaf, algorithm)).ToList(); + // ReSharper disable once HeapView.ObjectAllocation + List> nodes = []; + + // ReSharper disable once LoopCanBeConvertedToQuery, HeapView.ObjectAllocation.Possible, HeapView.ObjectAllocation.Evident + foreach (T leaf in leaves) nodes.Add(new MerkleTreeLeafNode(leaf, algorithm)); + Require(nodes.IsNotEmpty(), "Cannot construct a Merkle tree from an empty list.", nameof(leaves)); return BuildMerkleTree(nodes, algorithm); } @@ -45,14 +50,17 @@ private static MerkleTree BuildMerkleTree(IReadOnlyList> nodes, while (true) { if (nodes.IsSingle()) return nodes.Single(); + // ReSharper disable once HeapView.ObjectAllocation.Evident if (nodes.IsCountOdd()) nodes = nodes.Append(new MerkleTreeEmptyNode(algorithm)).ToList(); + // ReSharper disable once HeapView.ObjectAllocation List> mergedNodes = []; for (int index = 0; index < nodes.Count; index += 2) { MerkleTree left = nodes[index]; MerkleTree right = nodes[index + 1]; + // ReSharper disable once HeapView.ObjectAllocation.Evident mergedNodes.Add(new MerkleTreeBranchNode(left, right, algorithm)); } diff --git a/OnixLabs.Security.Cryptography/MerkleTree.Generic.Equatable.cs b/OnixLabs.Security.Cryptography/MerkleTree.Generic.Equatable.cs index 016015f..e91ed5e 100644 --- a/OnixLabs.Security.Cryptography/MerkleTree.Generic.Equatable.cs +++ b/OnixLabs.Security.Cryptography/MerkleTree.Generic.Equatable.cs @@ -44,7 +44,7 @@ public abstract partial class MerkleTree /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(MerkleTree left, MerkleTree right) => Equals(left, right); + public static bool operator ==(MerkleTree? left, MerkleTree? right) => Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -52,5 +52,5 @@ public abstract partial class MerkleTree /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(MerkleTree left, MerkleTree right) => !Equals(left, right); + public static bool operator !=(MerkleTree? left, MerkleTree? right) => !Equals(left, right); } diff --git a/OnixLabs.Security.Cryptography/MerkleTree.Generic.Get.cs b/OnixLabs.Security.Cryptography/MerkleTree.Generic.Get.cs index 71c216c..f5f6d4c 100644 --- a/OnixLabs.Security.Cryptography/MerkleTree.Generic.Get.cs +++ b/OnixLabs.Security.Cryptography/MerkleTree.Generic.Get.cs @@ -36,7 +36,8 @@ public MerkleTree ToMerkleTree(HashAlgorithm algorithm) /// Returns a new containing the leaf values from the current instance. public IReadOnlyList GetLeafValues() { - ICollection result = []; + // ReSharper disable once HeapView.ObjectAllocation + List result = []; CollectLeafValues(this, result); return result.ToImmutableList(); } @@ -46,6 +47,7 @@ public IReadOnlyList GetLeafValues() /// /// The current from which to begin iterating through. /// The collection that will contain the leaf hashes from the current . + // ReSharper disable TailRecursiveCall protected override void CollectLeafHashes(MerkleTree current, ICollection hashes) { switch (current) @@ -65,6 +67,7 @@ protected override void CollectLeafHashes(MerkleTree current, ICollection /// /// The current from which to begin iterating through. /// The list that will contain the leaf values from the current . + // ReSharper disable TailRecursiveCall private static void CollectLeafValues(MerkleTree current, ICollection values) { switch (current) diff --git a/OnixLabs.Security.Cryptography/MerkleTree.Get.cs b/OnixLabs.Security.Cryptography/MerkleTree.Get.cs index 17a3e72..7d9e37b 100644 --- a/OnixLabs.Security.Cryptography/MerkleTree.Get.cs +++ b/OnixLabs.Security.Cryptography/MerkleTree.Get.cs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -26,7 +25,8 @@ public abstract partial class MerkleTree /// Returns a new instance containing the leaf hashes from the current instance. public IReadOnlyList GetLeafHashes() { - ICollection result = []; + // ReSharper disable once HeapView.ObjectAllocation + List result = []; CollectLeafHashes(this, result); return result.ToImmutableList(); } @@ -36,6 +36,7 @@ public IReadOnlyList GetLeafHashes() /// /// The current from which to begin iterating through. /// The collection that will contain the leaf hashes from the current . + // ReSharper disable TailRecursiveCall protected virtual void CollectLeafHashes(MerkleTree current, ICollection hashes) { switch (current) diff --git a/OnixLabs.Security.Cryptography/MerkleTree.To.cs b/OnixLabs.Security.Cryptography/MerkleTree.To.cs index 1393873..1c41968 100644 --- a/OnixLabs.Security.Cryptography/MerkleTree.To.cs +++ b/OnixLabs.Security.Cryptography/MerkleTree.To.cs @@ -25,6 +25,12 @@ public abstract partial class MerkleTree /// Return the underlying representation of the current instance. public byte[] ToByteArray() => Hash.ToByteArray(); + /// + /// Gets the underlying representation of the current instance as a new instance. + /// + /// Return the underlying representation of the current instance as a new instance. + public ReadOnlySpan ToReadOnlySpan() => Hash.ToReadOnlySpan(); + /// /// Returns a that represents the current object. /// diff --git a/OnixLabs.Security.Cryptography/MerkleTree.cs b/OnixLabs.Security.Cryptography/MerkleTree.cs index 7da96eb..56b561b 100644 --- a/OnixLabs.Security.Cryptography/MerkleTree.cs +++ b/OnixLabs.Security.Cryptography/MerkleTree.cs @@ -13,13 +13,14 @@ // limitations under the License. using System.Security.Cryptography; +using OnixLabs.Core; namespace OnixLabs.Security.Cryptography; /// /// Represents a Merkle tree. /// -public abstract partial class MerkleTree : ICryptoPrimitive +public abstract partial class MerkleTree : ICryptoPrimitive, ISpanBinaryConvertible { /// /// Initializes a new instance of the class. diff --git a/OnixLabs.Security.Cryptography/NamedHash.To.cs b/OnixLabs.Security.Cryptography/NamedHash.To.cs index 0e4abff..880b472 100644 --- a/OnixLabs.Security.Cryptography/NamedHash.To.cs +++ b/OnixLabs.Security.Cryptography/NamedHash.To.cs @@ -21,16 +21,24 @@ public readonly partial record struct NamedHash { /// /// Gets the underlying representation of the underlying instance. + /// This method only obtains the bytes representing the hash value. The name of the hash will not be encoded into the resulting byte array. /// /// Return the underlying representation of the underlying instance. public byte[] ToByteArray() => Hash.ToByteArray(); + /// + /// Gets the underlying representation of the underlying instance as a new instance. + /// This method only obtains the bytes representing the hash value. The name of the hash will not be encoded into the resulting span. + /// + /// Return the underlying representation of the underlying instance as a new instance. + public ReadOnlySpan ToReadOnlySpan() => Hash.ToReadOnlySpan(); + /// /// Returns a that represents the current object. /// /// The format provider that will be used to determine the format of the string. /// Returns a that represents the current object. - public string ToString(IFormatProvider provider) => $"{AlgorithmName}:{IBaseCodec.GetString(ToByteArray(), provider)}"; + public string ToString(IFormatProvider provider) => string.Concat(AlgorithmName, Separator, IBaseCodec.GetString(ToByteArray(), provider)); /// /// Returns a that represents the current object. diff --git a/OnixLabs.Security.Cryptography/NamedHash.cs b/OnixLabs.Security.Cryptography/NamedHash.cs index d709610..385b168 100644 --- a/OnixLabs.Security.Cryptography/NamedHash.cs +++ b/OnixLabs.Security.Cryptography/NamedHash.cs @@ -13,14 +13,16 @@ // limitations under the License. using System.Security.Cryptography; +using OnixLabs.Core; namespace OnixLabs.Security.Cryptography; /// /// Represents a named cryptographic hash. /// -public readonly partial record struct NamedHash : ICryptoPrimitive +public readonly partial record struct NamedHash : ICryptoPrimitive, ISpanBinaryConvertible { + private const string Separator = ":"; private const string HashAlgorithmNameNullOrWhiteSpace = "Hash algorithm name must not be null or whitespace."; /// @@ -48,10 +50,12 @@ public NamedHash(Hash hash, HashAlgorithmName algorithmName) /// /// Gets the underlying hash value. /// + // ReSharper disable once MemberCanBePrivate.Global public Hash Hash { get; } /// /// Gets name of the hash algorithm that was used to produce the associated hash. /// + // ReSharper disable once MemberCanBePrivate.Global public string AlgorithmName { get; } } diff --git a/OnixLabs.Security.Cryptography/NamedPrivateKey.To.cs b/OnixLabs.Security.Cryptography/NamedPrivateKey.To.cs index 5c1bd09..61f9488 100644 --- a/OnixLabs.Security.Cryptography/NamedPrivateKey.To.cs +++ b/OnixLabs.Security.Cryptography/NamedPrivateKey.To.cs @@ -20,9 +20,9 @@ namespace OnixLabs.Security.Cryptography; public readonly partial record struct NamedPrivateKey { /// - /// Gets the underlying representation of the underlying instance. + /// Gets the underlying representation of the underlying instance. /// - /// Return the underlying representation of the underlying instance. + /// Return the underlying representation of the underlying instance. public byte[] ToByteArray() => PrivateKey.ToByteArray(); /// @@ -30,7 +30,7 @@ public readonly partial record struct NamedPrivateKey /// /// The format provider that will be used to determine the format of the string. /// Returns a that represents the current object. - public string ToString(IFormatProvider provider) => $"{AlgorithmName}:{IBaseCodec.GetString(ToByteArray(), provider)}"; + public string ToString(IFormatProvider provider) => string.Concat(AlgorithmName, Separator, IBaseCodec.GetString(ToByteArray(), provider)); /// /// Returns a that represents the current object. diff --git a/OnixLabs.Security.Cryptography/NamedPrivateKey.cs b/OnixLabs.Security.Cryptography/NamedPrivateKey.cs index 501dfee..58dd4c8 100644 --- a/OnixLabs.Security.Cryptography/NamedPrivateKey.cs +++ b/OnixLabs.Security.Cryptography/NamedPrivateKey.cs @@ -19,6 +19,7 @@ namespace OnixLabs.Security.Cryptography; /// public readonly partial record struct NamedPrivateKey : ICryptoPrimitive { + private const string Separator = ":"; private const string KeyAlgorithmNameNullOrWhiteSpace = "Key algorithm name must not be null or whitespace."; /// @@ -35,10 +36,12 @@ public NamedPrivateKey(PrivateKey privateKey, string algorithmName) /// /// Gets the underlying private key value. /// + // ReSharper disable once MemberCanBePrivate.Global public PrivateKey PrivateKey { get; } /// /// Gets the name of the key algorithm that was used to produce the associated private key. /// + // ReSharper disable once MemberCanBePrivate.Global public string AlgorithmName { get; } } diff --git a/OnixLabs.Security.Cryptography/NamedPublicKey.To.cs b/OnixLabs.Security.Cryptography/NamedPublicKey.To.cs index 70e7e44..db64c4e 100644 --- a/OnixLabs.Security.Cryptography/NamedPublicKey.To.cs +++ b/OnixLabs.Security.Cryptography/NamedPublicKey.To.cs @@ -21,16 +21,24 @@ public readonly partial record struct NamedPublicKey { /// /// Gets the underlying representation of the underlying instance. + /// This method only obtains the bytes representing the public key value. The name of the public key will not be encoded into the resulting byte array. /// /// Return the underlying representation of the underlying instance. public byte[] ToByteArray() => PublicKey.ToByteArray(); + /// + /// Gets the underlying representation of the current instance as a new instance. + /// This method only obtains the bytes representing the public key value. The name of the public key will not be encoded into the resulting span. + /// + /// Return the underlying representation of the current instance as a new instance. + public ReadOnlySpan ToReadOnlySpan() => PublicKey.ToReadOnlySpan(); + /// /// Returns a that represents the current object. /// /// The format provider that will be used to determine the format of the string. /// Returns a that represents the current object. - public string ToString(IFormatProvider provider) => $"{AlgorithmName}:{IBaseCodec.GetString(ToByteArray(), provider)}"; + public string ToString(IFormatProvider provider) => string.Concat(AlgorithmName, Separator, IBaseCodec.GetString(ToByteArray(), provider)); /// /// Returns a that represents the current object. diff --git a/OnixLabs.Security.Cryptography/NamedPublicKey.cs b/OnixLabs.Security.Cryptography/NamedPublicKey.cs index 35289a2..2ae270c 100644 --- a/OnixLabs.Security.Cryptography/NamedPublicKey.cs +++ b/OnixLabs.Security.Cryptography/NamedPublicKey.cs @@ -12,13 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +using OnixLabs.Core; + namespace OnixLabs.Security.Cryptography; /// /// Represents a named cryptographic public key. /// -public readonly partial record struct NamedPublicKey : ICryptoPrimitive +public readonly partial record struct NamedPublicKey : ICryptoPrimitive, ISpanBinaryConvertible { + private const string Separator = ":"; private const string KeyAlgorithmNameNullOrWhiteSpace = "Key algorithm name must not be null or whitespace."; /// @@ -35,10 +38,12 @@ public NamedPublicKey(PublicKey publicKey, string algorithmName) /// /// Gets the underlying public key value. /// + // ReSharper disable once MemberCanBePrivate.Global public PublicKey PublicKey { get; } /// /// Gets the name of the key algorithm that was used to produce the associated public key. /// + // ReSharper disable once MemberCanBePrivate.Global public string AlgorithmName { get; } } diff --git a/OnixLabs.Security.Cryptography/OnixLabs.Security.Cryptography.csproj b/OnixLabs.Security.Cryptography/OnixLabs.Security.Cryptography.csproj index d81efc8..b783afb 100644 --- a/OnixLabs.Security.Cryptography/OnixLabs.Security.Cryptography.csproj +++ b/OnixLabs.Security.Cryptography/OnixLabs.Security.Cryptography.csproj @@ -4,13 +4,13 @@ OnixLabs.Security.Cryptography ONIXLabs ONIXLabs Cryptography API for .NET - 8.16.0 + 9.0.0 en enable true Copyright © ONIXLabs 2020 https://github.com/onix-labs/onixlabs-dotnet - 8.16.0 + 9.0.0 12 diff --git a/OnixLabs.Security.Cryptography/PrivateKey.Equatable.cs b/OnixLabs.Security.Cryptography/PrivateKey.Equatable.cs index 88f2209..6d0effe 100644 --- a/OnixLabs.Security.Cryptography/PrivateKey.Equatable.cs +++ b/OnixLabs.Security.Cryptography/PrivateKey.Equatable.cs @@ -52,7 +52,7 @@ public bool Equals(PrivateKey? other) /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(PrivateKey left, PrivateKey right) => Equals(left, right); + public static bool operator ==(PrivateKey? left, PrivateKey? right) => Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -60,5 +60,5 @@ public bool Equals(PrivateKey? other) /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(PrivateKey left, PrivateKey right) => !Equals(left, right); + public static bool operator !=(PrivateKey? left, PrivateKey? right) => !Equals(left, right); } diff --git a/OnixLabs.Security.Cryptography/PrivateKey.To.cs b/OnixLabs.Security.Cryptography/PrivateKey.To.cs index fae42b8..41f0a45 100644 --- a/OnixLabs.Security.Cryptography/PrivateKey.To.cs +++ b/OnixLabs.Security.Cryptography/PrivateKey.To.cs @@ -13,7 +13,6 @@ // limitations under the License. using System; -using System.ComponentModel; using OnixLabs.Core; using OnixLabs.Core.Text; @@ -27,15 +26,6 @@ public abstract partial class PrivateKey /// Return the underlying representation of the current instance. public byte[] ToByteArray() => KeyData.Copy(); - /// - /// Creates a new from the current instance. - /// - /// The name of the algorithm that was used to produce the associated private key. - /// Returns a new from the current instance. - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Passing custom algorithm names can lead to named keys. Use the parameterless ToNamedPrivateKey method instead.")] - public NamedPrivateKey ToNamedPrivateKey(string algorithmName) => new(this, algorithmName); - /// /// Creates a new from the current instance. /// diff --git a/OnixLabs.Security.Cryptography/PrivateKey.cs b/OnixLabs.Security.Cryptography/PrivateKey.cs index 1e643e6..01417e6 100644 --- a/OnixLabs.Security.Cryptography/PrivateKey.cs +++ b/OnixLabs.Security.Cryptography/PrivateKey.cs @@ -13,7 +13,6 @@ // limitations under the License. using System; -using System.Buffers; namespace OnixLabs.Security.Cryptography; @@ -22,6 +21,7 @@ namespace OnixLabs.Security.Cryptography; /// public abstract partial class PrivateKey : ICryptoPrimitive { + // ReSharper disable once HeapView.ObjectAllocation.Evident private readonly ProtectedData protectedData = new(); private readonly byte[] encryptedKeyData; diff --git a/OnixLabs.Security.Cryptography/ProtectedData.cs b/OnixLabs.Security.Cryptography/ProtectedData.cs index e7d434a..a36b4b7 100644 --- a/OnixLabs.Security.Cryptography/ProtectedData.cs +++ b/OnixLabs.Security.Cryptography/ProtectedData.cs @@ -14,6 +14,7 @@ using System.IO; using System.Security.Cryptography; +using OnixLabs.Core.Linq; using Aes = System.Security.Cryptography.Aes; namespace OnixLabs.Security.Cryptography; @@ -21,6 +22,7 @@ namespace OnixLabs.Security.Cryptography; /// /// Represents an in-memory data protection mechanism for sensitive, long-lived cryptographic data. /// +// ReSharper disable HeapView.ObjectAllocation.Evident internal sealed class ProtectedData { private readonly byte[] key = Salt.CreateNonZero(32).ToByteArray(); @@ -33,7 +35,7 @@ internal sealed class ProtectedData /// Returns the encrypted data. public byte[] Encrypt(byte[] data) { - Require(data.Length > 0, "Data must not be empty.", nameof(data)); + if (data.IsEmpty()) return data; using Aes algorithm = Aes.Create(); @@ -59,7 +61,7 @@ public byte[] Encrypt(byte[] data) /// Returns the decrypted data. public byte[] Decrypt(byte[] data) { - Require(data.Length > 0, "Data must not be empty.", nameof(data)); + if (data.IsEmpty()) return data; using Aes algorithm = Aes.Create(); diff --git a/OnixLabs.Security.Cryptography/PublicKey.Equatable.cs b/OnixLabs.Security.Cryptography/PublicKey.Equatable.cs index 246507a..a945851 100644 --- a/OnixLabs.Security.Cryptography/PublicKey.Equatable.cs +++ b/OnixLabs.Security.Cryptography/PublicKey.Equatable.cs @@ -52,7 +52,7 @@ public bool Equals(PublicKey? other) /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(PublicKey left, PublicKey right) => Equals(left, right); + public static bool operator ==(PublicKey? left, PublicKey? right) => Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -60,5 +60,5 @@ public bool Equals(PublicKey? other) /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(PublicKey left, PublicKey right) => !Equals(left, right); + public static bool operator !=(PublicKey? left, PublicKey? right) => !Equals(left, right); } diff --git a/OnixLabs.Security.Cryptography/PublicKey.To.cs b/OnixLabs.Security.Cryptography/PublicKey.To.cs index 9020dad..e303706 100644 --- a/OnixLabs.Security.Cryptography/PublicKey.To.cs +++ b/OnixLabs.Security.Cryptography/PublicKey.To.cs @@ -27,6 +27,12 @@ public abstract partial class PublicKey /// Return the underlying representation of the current instance. public byte[] ToByteArray() => KeyData.Copy(); + /// + /// Gets the underlying representation of the current instance as a new instance. + /// + /// Return the underlying representation of the current instance as a new instance. + public ReadOnlySpan ToReadOnlySpan() => KeyData; + /// /// Creates a new from the current instance. /// diff --git a/OnixLabs.Security.Cryptography/PublicKey.cs b/OnixLabs.Security.Cryptography/PublicKey.cs index a1bbd71..88b44df 100644 --- a/OnixLabs.Security.Cryptography/PublicKey.cs +++ b/OnixLabs.Security.Cryptography/PublicKey.cs @@ -13,6 +13,7 @@ // limitations under the License. using System; +using OnixLabs.Core; namespace OnixLabs.Security.Cryptography; @@ -20,7 +21,7 @@ namespace OnixLabs.Security.Cryptography; /// Represents a cryptographic public key. /// /// The underlying key data of the cryptographic public key. -public abstract partial class PublicKey(ReadOnlySpan keyData) : ICryptoPrimitive +public abstract partial class PublicKey(ReadOnlySpan keyData) : ICryptoPrimitive, ISpanBinaryConvertible { /// /// Gets the cryptographic public key data. diff --git a/OnixLabs.Security.Cryptography/RsaPrivateKey.Convertible.cs b/OnixLabs.Security.Cryptography/RsaPrivateKey.Convertible.cs index 29bf8e6..2bd404f 100644 --- a/OnixLabs.Security.Cryptography/RsaPrivateKey.Convertible.cs +++ b/OnixLabs.Security.Cryptography/RsaPrivateKey.Convertible.cs @@ -17,6 +17,7 @@ namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed partial class RsaPrivateKey { /// diff --git a/OnixLabs.Security.Cryptography/RsaPrivateKey.Create.cs b/OnixLabs.Security.Cryptography/RsaPrivateKey.Create.cs index ac6062c..39e6781 100644 --- a/OnixLabs.Security.Cryptography/RsaPrivateKey.Create.cs +++ b/OnixLabs.Security.Cryptography/RsaPrivateKey.Create.cs @@ -16,6 +16,7 @@ namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed partial class RsaPrivateKey { /// diff --git a/OnixLabs.Security.Cryptography/RsaPrivateKey.Get.cs b/OnixLabs.Security.Cryptography/RsaPrivateKey.Get.cs index 75b30dd..aacfe6a 100644 --- a/OnixLabs.Security.Cryptography/RsaPrivateKey.Get.cs +++ b/OnixLabs.Security.Cryptography/RsaPrivateKey.Get.cs @@ -26,6 +26,7 @@ public RsaPublicKey GetPublicKey() { using RSA key = ImportKeyData(); byte[] keyData = key.ExportRSAPublicKey(); + // ReSharper disable once HeapView.ObjectAllocation.Evident return new RsaPublicKey(keyData); } } diff --git a/OnixLabs.Security.Cryptography/RsaPrivateKey.Import.cs b/OnixLabs.Security.Cryptography/RsaPrivateKey.Import.cs index ae95cb7..5c7f65b 100644 --- a/OnixLabs.Security.Cryptography/RsaPrivateKey.Import.cs +++ b/OnixLabs.Security.Cryptography/RsaPrivateKey.Import.cs @@ -18,6 +18,7 @@ namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed partial class RsaPrivateKey { /// diff --git a/OnixLabs.Security.Cryptography/RsaPrivateKey.Parse.cs b/OnixLabs.Security.Cryptography/RsaPrivateKey.Parse.cs index fce2b8d..c0b0ab9 100644 --- a/OnixLabs.Security.Cryptography/RsaPrivateKey.Parse.cs +++ b/OnixLabs.Security.Cryptography/RsaPrivateKey.Parse.cs @@ -64,6 +64,7 @@ public static RsaPrivateKey Parse(ReadOnlySpan value, IFormatProvider? pro public static bool TryParse(ReadOnlySpan value, IFormatProvider? provider, out RsaPrivateKey result) { bool isDecoded = IBaseCodec.TryGetBytes(value, provider ?? Base16FormatProvider.Invariant, out byte[] bytes); + // ReSharper disable once HeapView.ObjectAllocation.Evident result = new RsaPrivateKey(bytes); return isDecoded; } diff --git a/OnixLabs.Security.Cryptography/RsaPrivateKey.cs b/OnixLabs.Security.Cryptography/RsaPrivateKey.cs index ee67cbc..a096abc 100644 --- a/OnixLabs.Security.Cryptography/RsaPrivateKey.cs +++ b/OnixLabs.Security.Cryptography/RsaPrivateKey.cs @@ -27,6 +27,7 @@ public sealed partial class RsaPrivateKey(ReadOnlySpan keyData) : PrivateK /// Initializes a new instance of the struct. /// /// The with which to initialize the instance. + // ReSharper disable once MemberCanBePrivate.Global public RsaPrivateKey(ReadOnlySequence value) : this(value.ToArray()) { } diff --git a/OnixLabs.Security.Cryptography/RsaPublicKey.Convertible.cs b/OnixLabs.Security.Cryptography/RsaPublicKey.Convertible.cs index e9653c8..8304dcc 100644 --- a/OnixLabs.Security.Cryptography/RsaPublicKey.Convertible.cs +++ b/OnixLabs.Security.Cryptography/RsaPublicKey.Convertible.cs @@ -17,6 +17,7 @@ namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed partial class RsaPublicKey { /// diff --git a/OnixLabs.Security.Cryptography/RsaPublicKey.Parse.cs b/OnixLabs.Security.Cryptography/RsaPublicKey.Parse.cs index caf79a6..eb830c5 100644 --- a/OnixLabs.Security.Cryptography/RsaPublicKey.Parse.cs +++ b/OnixLabs.Security.Cryptography/RsaPublicKey.Parse.cs @@ -64,6 +64,7 @@ public static RsaPublicKey Parse(ReadOnlySpan value, IFormatProvider? prov public static bool TryParse(ReadOnlySpan value, IFormatProvider? provider, out RsaPublicKey result) { bool isDecoded = IBaseCodec.TryGetBytes(value, provider ?? Base16FormatProvider.Invariant, out byte[] bytes); + // ReSharper disable once HeapView.ObjectAllocation.Evident result = new RsaPublicKey(bytes); return isDecoded; } diff --git a/OnixLabs.Security.Cryptography/RsaPublicKey.cs b/OnixLabs.Security.Cryptography/RsaPublicKey.cs index 0a3ac7a..27da859 100644 --- a/OnixLabs.Security.Cryptography/RsaPublicKey.cs +++ b/OnixLabs.Security.Cryptography/RsaPublicKey.cs @@ -27,6 +27,7 @@ public sealed partial class RsaPublicKey(ReadOnlySpan keyData) : PublicKey /// Initializes a new instance of the struct. /// /// The with which to initialize the instance. + // ReSharper disable once MemberCanBePrivate.Global public RsaPublicKey(ReadOnlySequence value) : this(value.ToArray()) { } diff --git a/OnixLabs.Security.Cryptography/Salt.Create.cs b/OnixLabs.Security.Cryptography/Salt.Create.cs index f694002..ce48d29 100644 --- a/OnixLabs.Security.Cryptography/Salt.Create.cs +++ b/OnixLabs.Security.Cryptography/Salt.Create.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using System.Security.Cryptography; namespace OnixLabs.Security.Cryptography; @@ -26,7 +27,7 @@ public readonly partial struct Salt public static Salt Create(int length) { using RandomNumberGenerator generator = RandomNumberGenerator.Create(); - byte[] value = new byte[length]; + Span value = stackalloc byte[length]; generator.GetBytes(value); return new Salt(value); } @@ -39,7 +40,7 @@ public static Salt Create(int length) public static Salt CreateNonZero(int length) { using RandomNumberGenerator generator = RandomNumberGenerator.Create(); - byte[] value = new byte[length]; + Span value = stackalloc byte[length]; generator.GetNonZeroBytes(value); return new Salt(value); } diff --git a/OnixLabs.Security.Cryptography/Salt.Equatable.cs b/OnixLabs.Security.Cryptography/Salt.Equatable.cs index 8e00cfb..6e4e3cf 100644 --- a/OnixLabs.Security.Cryptography/Salt.Equatable.cs +++ b/OnixLabs.Security.Cryptography/Salt.Equatable.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Linq; +using System.Collections.Generic; using OnixLabs.Core.Linq; namespace OnixLabs.Security.Cryptography; @@ -45,7 +45,7 @@ public readonly partial struct Salt /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Salt left, Salt right) => Equals(left, right); + public static bool operator ==(Salt left, Salt right) => EqualityComparer.Default.Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -53,5 +53,5 @@ public readonly partial struct Salt /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Salt left, Salt right) => !Equals(left, right); + public static bool operator !=(Salt left, Salt right) => !EqualityComparer.Default.Equals(left, right); } diff --git a/OnixLabs.Security.Cryptography/Salt.To.cs b/OnixLabs.Security.Cryptography/Salt.To.cs index 9a5bd31..e7668bb 100644 --- a/OnixLabs.Security.Cryptography/Salt.To.cs +++ b/OnixLabs.Security.Cryptography/Salt.To.cs @@ -26,6 +26,12 @@ public readonly partial struct Salt /// Return the underlying representation of the current instance. public byte[] ToByteArray() => value.Copy(); + /// + /// Gets the underlying representation of the current instance as a new instance. + /// + /// Return the underlying representation of the current instance as a new instance. + public ReadOnlySpan ToReadOnlySpan() => value; + /// /// Returns a that represents the current object. /// diff --git a/OnixLabs.Security.Cryptography/Salt.cs b/OnixLabs.Security.Cryptography/Salt.cs index b773cde..fcd028b 100644 --- a/OnixLabs.Security.Cryptography/Salt.cs +++ b/OnixLabs.Security.Cryptography/Salt.cs @@ -13,6 +13,7 @@ // limitations under the License. using System; +using OnixLabs.Core; namespace OnixLabs.Security.Cryptography; @@ -20,7 +21,7 @@ namespace OnixLabs.Security.Cryptography; /// Represents a cryptographically secure random number, otherwise known as a salt value. /// /// The underlying value of the salt. -public readonly partial struct Salt(ReadOnlySpan value) : ICryptoPrimitive +public readonly partial struct Salt(ReadOnlySpan value) : ICryptoPrimitive, ISpanBinaryConvertible { private readonly byte[] value = value.ToArray(); } diff --git a/OnixLabs.Security.Cryptography/Secret.Equatable.cs b/OnixLabs.Security.Cryptography/Secret.Equatable.cs index 747ffc4..a50793a 100644 --- a/OnixLabs.Security.Cryptography/Secret.Equatable.cs +++ b/OnixLabs.Security.Cryptography/Secret.Equatable.cs @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Linq; -using OnixLabs.Core.Linq; +using System.Collections.Generic; namespace OnixLabs.Security.Cryptography; @@ -45,7 +44,7 @@ public readonly partial struct Secret /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(Secret left, Secret right) => Equals(left, right); + public static bool operator ==(Secret left, Secret right) => EqualityComparer.Default.Equals(left, right); /// /// Performs an inequality comparison between two object instances. @@ -53,5 +52,5 @@ public readonly partial struct Secret /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(Secret left, Secret right) => !Equals(left, right); + public static bool operator !=(Secret left, Secret right) => !EqualityComparer.Default.Equals(left, right); } diff --git a/OnixLabs.Security.Cryptography/Secret.Parse.cs b/OnixLabs.Security.Cryptography/Secret.Parse.cs index 3178c1f..c8066e0 100644 --- a/OnixLabs.Security.Cryptography/Secret.Parse.cs +++ b/OnixLabs.Security.Cryptography/Secret.Parse.cs @@ -63,8 +63,13 @@ public static Secret Parse(ReadOnlySpan value, IFormatProvider? provider = /// Returns if the specified value was decoded successfully; otherwise, . public static bool TryParse(ReadOnlySpan value, IFormatProvider? provider, out Secret result) { - bool isDecoded = IBaseCodec.TryGetBytes(value, provider ?? Base16FormatProvider.Invariant, out byte[] bytes); - result = new Secret(bytes); - return isDecoded; + if (IBaseCodec.TryGetBytes(value, provider ?? Base16FormatProvider.Invariant, out byte[] bytes)) + { + result = new Secret(bytes); + return true; + } + + result = default; + return false; } } diff --git a/OnixLabs.Security.Cryptography/Secret.cs b/OnixLabs.Security.Cryptography/Secret.cs index abd6496..2a276d3 100644 --- a/OnixLabs.Security.Cryptography/Secret.cs +++ b/OnixLabs.Security.Cryptography/Secret.cs @@ -22,6 +22,7 @@ namespace OnixLabs.Security.Cryptography; /// public readonly partial struct Secret : ICryptoPrimitive, ISpanParsable { + // ReSharper disable once HeapView.ObjectAllocation.Evident private readonly ProtectedData protectedData = new(); private readonly byte[] encryptedValue; private readonly Hash hash; diff --git a/OnixLabs.Security.Cryptography/Sha3.Create.cs b/OnixLabs.Security.Cryptography/Sha3.Create.cs index 5140735..0112a39 100644 --- a/OnixLabs.Security.Cryptography/Sha3.Create.cs +++ b/OnixLabs.Security.Cryptography/Sha3.Create.cs @@ -14,6 +14,7 @@ namespace OnixLabs.Security.Cryptography; +// ReSharper disable HeapView.ObjectAllocation.Evident public abstract partial class Sha3 { /// diff --git a/OnixLabs.Security.Cryptography/Sha3.Permute.cs b/OnixLabs.Security.Cryptography/Sha3.Permute.cs index e93905a..776927e 100644 --- a/OnixLabs.Security.Cryptography/Sha3.Permute.cs +++ b/OnixLabs.Security.Cryptography/Sha3.Permute.cs @@ -28,6 +28,7 @@ private static void Permute(IList state) ulong c0, c1, c2, c3, c4, d0, d1, d2, d3, d4; + // ReSharper disable once HeapView.ObjectAllocation ulong[] roundConstants = [ 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000, diff --git a/OnixLabs.Security.Cryptography/Sha3.cs b/OnixLabs.Security.Cryptography/Sha3.cs index 16be5f7..bdb14d1 100644 --- a/OnixLabs.Security.Cryptography/Sha3.cs +++ b/OnixLabs.Security.Cryptography/Sha3.cs @@ -82,20 +82,15 @@ protected Sha3(int rateBytes, int delimiter, int bitLength) { this.rateBytes = rateBytes; this.delimiter = delimiter; - - HashSize = bitLength; + HashSizeValue = bitLength; } - /// - /// Gets the size, in bits, of the computed hash code. - /// - public override int HashSize { get; } - /// /// Initializes an implementation of the class. /// public override void Initialize() { + // ReSharper disable HeapView.ObjectAllocation.Evident blockSize = default; inputPointer = default; outputPointer = default; @@ -127,6 +122,7 @@ protected override void HashCore(byte[] array, int ibStart, int cbSize) cbSize -= blockSize; if (blockSize != rateBytes) continue; + Permute(state); blockSize = 0; } @@ -141,10 +137,7 @@ protected override byte[] HashFinal() byte pad = Convert.ToByte(Buffer.GetByte(state, blockSize) ^ delimiter); Buffer.SetByte(state, blockSize, pad); - if ((delimiter & 0x80) != 0 && blockSize == rateBytes - 1) - { - Permute(state); - } + if ((delimiter & 0x80) != 0 && blockSize == rateBytes - 1) Permute(state); pad = Convert.ToByte(Buffer.GetByte(state, rateBytes - 1) ^ 0x80); Buffer.SetByte(state, rateBytes - 1, pad); @@ -159,10 +152,7 @@ protected override byte[] HashFinal() outputPointer += blockSize; outputBytesLeft -= blockSize; - if (outputBytesLeft > 0) - { - Permute(state); - } + if (outputBytesLeft > 0) Permute(state); } return result; diff --git a/OnixLabs.Security.UnitTests/SecurityTokenBuilderTests.cs b/OnixLabs.Security.UnitTests/SecurityTokenBuilderTests.cs index bd4d876..2a30a0f 100644 --- a/OnixLabs.Security.UnitTests/SecurityTokenBuilderTests.cs +++ b/OnixLabs.Security.UnitTests/SecurityTokenBuilderTests.cs @@ -652,7 +652,7 @@ public void SecurityTokenBuilderUseExtendedSpecialCharactersShouldProduceTheExpe [InlineData(16, 256, "R.%s+A5\"9@")] [InlineData(16, 721, "LI*Q&8/oqnSIA1!D")] [InlineData(16, 999, "7?f-w2W619ALLtx.")] - [InlineData(32, 0, "&{_0t0>P`zBR7S|c;\\@D{]\\d$X.#ZhrQ")] + [InlineData(32, 0, @"&{_0t0>P`zBR7S|c;\@D{]\d$X.#ZhrQ")] [InlineData(32, 4, "{\\1Mbf$QS(w3JZ+XZS+;G}!OijJp9C!V")] [InlineData(32, 7, "K;!eI@d/]:Q.Pk\"L=F;c(1qSKc/cT^_B")] [InlineData(32, 9, "ORfTzN?/i3,U}oA982,=bfWq2D@ydEV}")] diff --git a/OnixLabs.Security/OnixLabs.Security.csproj b/OnixLabs.Security/OnixLabs.Security.csproj index 8ceb12e..2c4a10b 100644 --- a/OnixLabs.Security/OnixLabs.Security.csproj +++ b/OnixLabs.Security/OnixLabs.Security.csproj @@ -4,13 +4,13 @@ OnixLabs.Security ONIXLabs ONIXLabs Security API for .NET - 8.16.0 + 9.0.0 en enable true Copyright © ONIXLabs 2020 https://github.com/onix-labs/onixlabs-dotnet - 8.16.0 + 9.0.0 12 diff --git a/OnixLabs.Security/RandomNumberProvider.cs b/OnixLabs.Security/RandomNumberProvider.cs index 2de6b1b..be400b4 100644 --- a/OnixLabs.Security/RandomNumberProvider.cs +++ b/OnixLabs.Security/RandomNumberProvider.cs @@ -20,6 +20,7 @@ namespace OnixLabs.Security; /// /// Represents a random number provider. /// +// ReSharper disable HeapView.ObjectAllocation.Evident internal abstract class RandomNumberProvider { /// diff --git a/OnixLabs.Security/SecurityToken.Equatable.cs b/OnixLabs.Security/SecurityToken.Equatable.cs index a089d93..4a1e188 100644 --- a/OnixLabs.Security/SecurityToken.Equatable.cs +++ b/OnixLabs.Security/SecurityToken.Equatable.cs @@ -44,7 +44,7 @@ public readonly partial struct SecurityToken /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static bool operator ==(SecurityToken left, SecurityToken right) => Equals(left, right); + public static bool operator ==(SecurityToken left, SecurityToken right) => left.Equals(right); /// /// Performs an inequality comparison between two object instances. @@ -52,5 +52,5 @@ public readonly partial struct SecurityToken /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static bool operator !=(SecurityToken left, SecurityToken right) => !Equals(left, right); + public static bool operator !=(SecurityToken left, SecurityToken right) => !left.Equals(right); } diff --git a/OnixLabs.Security/SecurityTokenBuilder.cs b/OnixLabs.Security/SecurityTokenBuilder.cs index b5c527c..4ed296c 100644 --- a/OnixLabs.Security/SecurityTokenBuilder.cs +++ b/OnixLabs.Security/SecurityTokenBuilder.cs @@ -21,6 +21,8 @@ namespace OnixLabs.Security; /// /// Represents a builder. /// +// ReSharper disable HeapView.ObjectAllocation +// ReSharper disable HeapView.ObjectAllocation.Evident public sealed class SecurityTokenBuilder { private const string LowerCaseCharacters = "abcdefghijklmnopqrstuvwxyz"; diff --git a/README.md b/README.md index ca44623..9aa39c6 100644 --- a/README.md +++ b/README.md @@ -2,33 +2,116 @@ # ONIXLabs .NET Library -Welcome to the ONIXLabs .NET Library, a comprehensive and meticulously crafted suite of APIs engineered to empower developers and enrich the developer experience. With the ONIXLabs .NET Library, developers gain access to a wealth of tools and resources aimed at streamlining development workflows, enhancing code quality, and fortifying application security. Whether you're a seasoned .NET developer or embarking on your coding journey, ONIXLabs is your trusted companion for building robust and secure software solutions. +The world of software development is ever-evolving, driven by the constant need for innovation, efficiency, and maintainability. In this landscape, libraries and frameworks play a crucial role in simplifying complex tasks, promoting code reuse, and fostering community collaboration. The ONIXLabs .NET Library is born out of this very ethos—a desire to create a robust, versatile, and community-friendly library that fills the gaps often encountered in various development projects. + +[![.NET](https://github.com/onix-labs/onixlabs-dotnet/actions/workflows/dotnet.yml/badge.svg)](https://github.com/onix-labs/onixlabs-dotnet/actions/workflows/dotnet.yml) ## ONIXLabs Core At the heart of the ONIXLabs .NET Library lies the Core module, serving as the bedrock for building resilient and scalable applications. Here, developers will find essential APIs and interfaces meticulously designed to promote code reusability, maintainability, and readability. From foundational interfaces to extension methods enriching object manipulation, ONIXLabs Core sets the stage for seamless development experiences. -- **Core APIs**: Includes interfaces such as `IValueEquatable`, `IValueComparable`, and `IBinaryConvertible`, providing a foundation for implementing common patterns and functionalities. -- **Extension Methods**: Extends the capabilities of objects, arrays, strings, and enumerables, making data manipulation more intuitive and efficient. -- **Strong Enumeration Pattern**: Offers an implementation of the strong enumeration pattern, for strongly typed enumerations which can be extended with value and associated behaviour. -- **Text Services**: Provides codecs for various encoding schemes like Base16, Base32, Base58, and Base64, along with primitive structs representing these bases for semantic clarity. +### Interfaces + +The ONIXLabs .NET Library includes interfaces such as `IValueEquatable`, `IValueComparable`, and `IBinaryConvertible`, providing a foundation for implementing common patterns and functionalities. + +### Extension Methods + +The ONIXLabs .NET Library extends the capabilities of objects, arrays, strings, sequences, comparables, and enumerables, making data manipulation more intuitive and efficient. + +### Preconditions + +The ONIXLabs .NET Library offers a comprehensive set of preconditions designed to facilitate clean, consistent, and reliable guard clauses. This promotes a "fail-fast" programming approach, ensuring that potential issues are identified and addressed early in the execution process. + +### Strong Enumeration Pattern + +The ONIXLabs .NET Library provides an abstraction of the strong enumeration pattern, allowing consumers to build strict, strongly typed enumerations which can be extended with value and associated behavior. + +### Functional Concepts + +The ONIXLabs .NET Library includes robust implementations of the `Result`, `Result`, and `Optional` patterns, which are widely used in functional programming languages. These implementations address two prevalent challenges in object-oriented programming: exception handling and null reference management. By integrating these patterns, the library provides more predictable and maintainable code structures. + +#### Result Pattern + +Enables methods to return a success or failure state, encapsulating both the outcome and the associated data or error message. This approach simplifies error handling and improves code readability. + +#### Optional Pattern + +Facilitates the handling of potentially null values, thereby reducing the risk of `NullReferenceException` and promoting safer code practices. + +#### Asynchronous Programming + +The ONIXLabs .NET Library extends these patterns with asynchronous programming support, including extension methods that allow `Result`, `Result`, and `Optional` to be seamlessly integrated into asynchronous workflows. This makes it easier to work with these patterns in modern, async-based applications, ensuring consistent and reliable error and null handling across synchronous and asynchronous contexts. + +### LINQ Extension Methods + +The ONIXLabs .NET Library enhances the standard LINQ capabilities with additional extension methods for working with `IEnumerable` and `IEnumerable`, making it easier to perform complex queries and manipulations on collections. + +### Text Encoding Services + +The ONIXLabs .NET Library provides codecs for various encoding schemes like `Base16`, `Base32`, `Base58`, and `Base64`, along with primitive structs representing these bases for semantic clarity. ## ONIXLabs Numerics -Numerical computations are a fundamental aspect of many software applications, often requiring precision, flexibility, and performance. The ONIXLabs Numerics module equips developers with a suite of tools tailored to meet these demands. Whether it's obtaining detailed insights into numeric values or performing complex arithmetic operations, this module empowers developers to tackle numerical challenges with ease and confidence. +Numerical computations are a fundamental aspect of many software applications, often requiring precision, flexibility, and performance. The ONIXLabs .NET Library Numerics module equips developers with a suite of tools tailored to meet these demands. Whether it's obtaining detailed insights into numeric values or performing complex arithmetic operations, this module empowers developers to tackle numerical challenges with ease and confidence. + +### Number Information + +The ONIXLabs .NET Library includes a versatile struct called `NumberInfo` that provides detailed insights into any numeric value, including unscaled value, scale, significand, exponent, precision, and sign. + +### Number Information Formatting -- **NumberInfo**: A versatile struct that provides detailed insights into any numeric value, including unscaled value, scale, significand, exponent, precision, and sign. -- **BigDecimal**: An arbitrary-length decimal number representation leveraging .NET's generic math interfaces. It supports a comprehensive range of arithmetic operations, parsing, conversion, and culture-specific formatting, facilitating precise numerical calculations. -- **Generic Math Extension Methods**: Extends the functionality of numerical types with useful generic math extension methods, enhancing flexibility and productivity in numerical computing tasks. +The ONIXLabs .NET Library features a comprehensive number formatting API that allows any number type convertible to `NumberInfo` to be formatted using various number styles. These styles include currency, decimal, exponential (scientific), fixed, general, number, and percentage formats. Additionally, the API supports automatic cultural formatting, ensuring that numbers are appropriately formatted according to different cultural conventions. + +### Big Decimal + +The ONIXLabs .NET Library provides an arbitrary-length `BigDecimal` number representation leveraging .NET's generic math interfaces. It supports a comprehensive range of arithmetic operations, parsing, conversion, and culture-specific formatting, facilitating precise numerical calculations. + +### Generic Math Extension Methods + +The ONIXLabs .NET Library extends the functionality of numerical types with useful generic math extension methods, enhancing flexibility and productivity in numerical computing tasks. + +## ONIXLabs Security + +The Security module in the ONIXLabs .NET Library focuses on non-cryptographic security measures, addressing various aspects of application security beyond encryption. This module provides tools and utilities to enhance the security of applications through mechanisms such as secure data handling, input validation, and access control. By implementing best practices and standards for secure coding, the Security module helps developers protect their applications from common vulnerabilities and threats, ensuring robust and reliable security across different components of the software. + +### Security Tokens + +The ONIXLabs .NET Library includes a powerful API for generating security tokens. These tokens are customizable sequences of random characters designed to function as temporary passwords or authentication codes. The API offers extensive configuration options, allowing developers to specify the length, character set, and complexity of the generated tokens to meet various security requirements. This flexibility ensures that the tokens can be tailored to different use cases, such as user verification, session management, or one-time password generation, thereby enhancing the security and usability of applications. ## ONIXLabs Cryptography -In an era marked by increasing cybersecurity threats, robust cryptographic implementations are paramount. The ONIXLabs Cryptography module offers developers a comprehensive set of APIs for handling cryptographic operations securely. From digital signatures and hash functions to Merkle trees and FIPS-202 compliant SHA-3 implementations, developers can leverage ONIXLabs Cryptography to safeguard sensitive data and ensure the integrity and confidentiality of their applications. +In an era marked by increasing cybersecurity threats, robust cryptographic implementations are paramount. The ONIXLabs Cryptography module offers developers a comprehensive set of APIs for handling cryptographic operations securely. From digital signatures and hash functions to Merkle trees and FIPS-202 compliant SHA3 implementations, developers can leverage ONIXLabs Cryptography to safeguard sensitive data and ensure the integrity and confidentiality of their applications. + +### Public/Private Key Cryptography + +The ONIXLabs .NET Library offers comprehensive support for public/private key cryptography, essential for secure communication and data encryption. The library includes abstractions such as `PublicKey` and `PrivateKey`, which encapsulate the raw byte data of cryptographic keys, providing a structured and secure way to handle key material. Additionally, the `NamedPublicKey` and `NamedPrivateKey` classes extend these abstractions by associating the keys with the name of the cryptographic algorithm used, enhancing clarity and ease of use in multi-algorithm contexts. + +The library supports various cryptographic algorithms through interfaces and concrete implementations, including ECDSA (Elliptic Curve Digital Signature Algorithm), ECDH (Elliptic Curve Diffie-Hellman), and RSA (Rivest-Shamir-Adleman). These implementations enable secure key generation, digital signing, encryption, and decryption operations. For instance, ECDSA and RSA are used for generating and verifying digital signatures, ensuring data integrity and authenticity, while ECDH facilitates secure key exchange mechanisms. + +By providing these abstractions and implementations, the ONIXLabs .NET Library ensures that developers can seamlessly integrate robust cryptographic functionality into their applications, leveraging the strengths of various cryptographic algorithms to meet diverse security requirements. This makes the library an invaluable tool for building secure, reliable, and high-performance applications in the .NET ecosystem. + +### Digital Signatures + +The ONIXLabs .NET Library provides several APIs for creating, storing, and verifying cryptographic digital signatures. The `DigitalSignature` struct is designed to encapsulate the underlying raw byte data of a cryptographic digital signature, ensuring the integrity and authenticity of digital messages, whilst the `DigitalSignatureAndPublicKey` struct combines a `DigitalSignature` with its corresponding `NamedPublicKey`, which is used to verify the authenticity of the digital signature. + +### Hashing + +The ONIXLabs .NET Library features a comprehensive set of APIs dedicated to creating and storing cryptographic hash values. At the core of these APIs is the `Hash` struct, which is designed to encapsulate the raw byte data of a cryptographic hash. This struct provides a robust and efficient way to handle hash values, ensuring data integrity and security across various applications. + +In addition to basic hashing capabilities, the ONIXLabs .NET Library includes a fully managed implementation of the FIPS-202 standardized SHA3 algorithm. This implementation covers all major variants of SHA3, including SHA3-224, SHA3-256, SHA3-384, and SHA3-512, as well as the extendable-output functions SHAKE128 and SHAKE256. By adhering to the FIPS-202 standards, the library ensures compliance with cryptographic standards. + +### Merkle Trees + +The ONIXLabs .NET Library includes implementations of the `MerkleTree` and `MerkleTree`, which are essential for constructing and managing Merkle trees. A Merkle tree is a cryptographic data structure that allows efficient and secure verification of the contents of large data sets. The `MerkleTree` class provides a general implementation, while `MerkleTree` offers a generic version, where `T` implements `IHashable`. These implementations support the creation of Merkle trees by recursively hashing pairs of nodes until a single root hash, known as the Merkle root, is obtained. This root hash can then be used to verify the integrity and consistency of the entire data set with minimal computational overhead. The library's implementation ensures compatibility with various hash functions, making it a versatile tool for applications requiring secure data verification, such as blockchain or distributed ledger technologies, file integrity checks, and distributed systems. + +### Salts + +The ONIXLabs .NET Library includes a `Salt` struct designed to encapsulate cryptographically secure random numbers, which are essential for various security operations. Salts are random data added to passwords or other data before hashing to ensure that identical inputs produce unique hash values. This approach prevents attackers from using precomputed tables, such as rainbow tables, to reverse-engineer hashed data. The `Salt` struct in ONIXLabs provides a simple yet powerful way to generate and manage these cryptographic salts, ensuring they are sufficiently random and secure. By incorporating salts into hashing and encryption workflows, the library significantly enhances security, making it more resistant to attacks and ensuring the robustness of applications against potential vulnerabilities. This struct is particularly useful in scenarios like password storage, token generation, and any application requiring enhanced cryptographic security. + +### Secrets + +The ONIXLabs .NET Library includes a `Secret` struct, designed to securely handle sensitive data such as passwords, encryption keys, and other confidential information. The `Secret` struct ensures that sensitive data is managed with the highest level of security by leveraging secure memory management techniques to minimize the risk of exposure. It provides methods for securely storing, accessing, and disposing of sensitive information, ensuring that data is encrypted in memory and cleared immediately after use. This approach prevents unauthorized access and reduces the risk of memory-based attacks. The `Secret` struct is particularly valuable in applications that require stringent security measures, such as authentication systems, encryption services, and any scenario where sensitive data must be protected. By incorporating the `Secret` struct, the ONIXLabs .NET Library helps developers build more secure applications, safeguarding critical information against potential breaches and ensuring compliance with security best practices. -- **DigitalSignature**: Wraps digital signatures, providing a convenient abstraction for cryptographic signature operations. -- **Hash**: Wraps hash functions, facilitating secure hashing of data with ease. -- **MerkleTree**: Represents Merkle trees, enabling efficient verification of data integrity in distributed systems. -- **Salt**: Represents cryptographically secure random numbers, enhancing security in cryptographic operations. -- **Public and Private Key Abstractions**: Offers abstractions for public and private keys, including implementations for RSA and ECDSA, along with functions for signing and verification. -- **SHA-3 Implementation**: A complete, platform-independent, FIPS-202 implementation of SHA-3, including variants like SHA-3 224, 256, 384, 512, SHAKE128, and SHAKE256, ensuring compliance with cryptographic standards. +### In-Memory Encryption +The ONIXLabs .NET Library ensures the highest level of security for sensitive data by implementing in-memory encryption for `Secret` and `PrivateKey` data. This technique encrypts these critical pieces of information while they are stored in memory, thereby protecting them from memory dump attacks, unauthorized access, and other forms of in-memory exploitation. By encrypting sensitive data in memory, the library reduces the risk of exposure even if an attacker gains access to the system's memory. This approach is particularly effective in defending against certain classes of attacks, such as those that exploit vulnerabilities to read memory content directly. The encrypted memory handling for `Secret` and `PrivateKey` data is a key feature that enhances the overall security posture of applications using the ONIXLabs .NET Library, ensuring that sensitive information remains confidential and secure throughout its lifecycle. diff --git a/gitlog.sh b/gitlog.sh new file mode 100755 index 0000000..5beef58 --- /dev/null +++ b/gitlog.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Ensure that the script stops if any command fails +set -e + +# Function to display usage instructions +usage() { + echo "Usage: $0 []" + exit 1 +} + +# Check if the correct number of arguments are provided +if [ "$#" -lt 3 ]; then + usage +fi + +REPO_PATH=$1 +START_TAG=$2 +END_TAG=$3 +GREP_PATTERN=${4:-} # Optional fourth argument for grep pattern + +# Change to the specified repository path +cd "$REPO_PATH" + +# Ensure we are in a git repository +if [ ! -d .git ]; then + echo "Error: $REPO_PATH is not a git repository." + exit 1 +fi + +# Fetch the commit messages between the specified tags, including all commits +if [ -z "$GREP_PATTERN" ]; then + # No grep pattern provided, just list the commit messages + git log --oneline "$START_TAG".."$END_TAG" +else + # Grep pattern provided, filter the commit messages + git log --oneline "$START_TAG".."$END_TAG" | grep "$GREP_PATTERN" +fi