Skip to content

Commit 8e46099

Browse files
authored
Merge pull request #124 from feO2x/issue-118-must-have-length-in-for-immutable-array
Issue 118 must have length in for immutable array
2 parents b235afb + ba8d727 commit 8e46099

File tree

4 files changed

+170
-0
lines changed

4 files changed

+170
-0
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using FluentAssertions;
4+
using Xunit;
5+
6+
namespace Light.GuardClauses.Tests.CollectionAssertions;
7+
8+
public static class MustHaveLengthInTests
9+
{
10+
[Theory]
11+
[MemberData(nameof(LengthInRangeData))]
12+
public static void LengthInRange(ImmutableArray<int> array, Range<int> range) =>
13+
array.MustHaveLengthIn(range).Should().Equal(array);
14+
15+
public static readonly TheoryData<ImmutableArray<int>, Range<int>> LengthInRangeData =
16+
new ()
17+
{
18+
{ [1, 2, 3], Range.FromInclusive(0).ToExclusive(10) },
19+
{ [1, 2, 3, 4, 5], Range.FromInclusive(3).ToInclusive(5) },
20+
{ ImmutableArray<int>.Empty, Range.FromInclusive(0).ToExclusive(100) },
21+
{ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], Range.FromExclusive(5).ToInclusive(10) },
22+
};
23+
24+
[Theory]
25+
[MemberData(nameof(LengthNotInRangeData))]
26+
public static void LengthNotInRange(ImmutableArray<int> array, Range<int> range)
27+
{
28+
var act = () => array.MustHaveLengthIn(range, nameof(array));
29+
30+
act.Should().Throw<ArgumentOutOfRangeException>()
31+
.And.Message.Should().Contain($"must have its length in between {range.CreateRangeDescriptionText("and")}")
32+
.And.Contain($"but it actually has length {array.Length}");
33+
}
34+
35+
public static readonly TheoryData<ImmutableArray<int>, Range<int>> LengthNotInRangeData =
36+
new ()
37+
{
38+
{ [1, 2, 3], Range.FromInclusive(10).ToInclusive(20) },
39+
{ [1, 2, 3, 4], Range.FromExclusive(4).ToExclusive(10) },
40+
{ ImmutableArray<int>.Empty, Range.FromInclusive(1).ToExclusive(50) },
41+
{ [1, 2], Range.FromInclusive(100).ToExclusive(256) },
42+
};
43+
44+
[Fact]
45+
public static void CustomException() =>
46+
Test.CustomException(
47+
ImmutableArray.Create(1, 2, 3),
48+
Range.FromInclusive(5).ToInclusive(10),
49+
(array, r, exceptionFactory) => array.MustHaveLengthIn(r, exceptionFactory)
50+
);
51+
52+
[Fact]
53+
public static void CustomMessage() =>
54+
Test.CustomMessage<ArgumentOutOfRangeException>(
55+
message => ImmutableArray.Create(1, 2, 3).MustHaveLengthIn(
56+
Range.FromInclusive(42).ToInclusive(50),
57+
message: message
58+
)
59+
);
60+
61+
[Fact]
62+
public static void CallerArgumentExpression()
63+
{
64+
var testArray = ImmutableArray.Create(1, 2, 3, 4, 5);
65+
66+
var act = () => testArray.MustHaveLengthIn(Range.FromInclusive(10).ToExclusive(20));
67+
68+
act.Should().Throw<ArgumentOutOfRangeException>()
69+
.WithParameterName(nameof(testArray));
70+
}
71+
}

Code/Light.GuardClauses/Check.MustHaveLengthIn.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Immutable;
23
using System.Runtime.CompilerServices;
34
using JetBrains.Annotations;
45
using Light.GuardClauses.ExceptionFactory;
@@ -57,4 +58,51 @@ public static string MustHaveLengthIn(
5758

5859
return parameter;
5960
}
61+
62+
/// <summary>
63+
/// Ensures that the <see cref="ImmutableArray{T}" />'s length is within the specified range, or otherwise throws an <see cref="ArgumentOutOfRangeException" />.
64+
/// </summary>
65+
/// <param name="parameter">The <see cref="ImmutableArray{T}" /> to be checked.</param>
66+
/// <param name="range">The range where the <see cref="ImmutableArray{T}" />'s length must be in-between.</param>
67+
/// <param name="parameterName">The name of the parameter (optional).</param>
68+
/// <param name="message">The message that will be passed to the resulting exception (optional).</param>
69+
/// <exception cref="ArgumentOutOfRangeException">Thrown when the length of <paramref name="parameter" /> is not within the specified <paramref name="range" />.</exception>
70+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
71+
public static ImmutableArray<T> MustHaveLengthIn<T>(
72+
this ImmutableArray<T> parameter,
73+
Range<int> range,
74+
[CallerArgumentExpression("parameter")] string? parameterName = null,
75+
string? message = null
76+
)
77+
{
78+
if (!range.IsValueWithinRange(parameter.Length))
79+
{
80+
Throw.ImmutableArrayLengthNotInRange(parameter, range, parameterName, message);
81+
}
82+
83+
return parameter;
84+
}
85+
86+
/// <summary>
87+
/// Ensures that the <see cref="ImmutableArray{T}" />'s length is within the specified range, or otherwise throws your custom exception.
88+
/// </summary>
89+
/// <param name="parameter">The <see cref="ImmutableArray{T}" /> to be checked.</param>
90+
/// <param name="range">The range where the <see cref="ImmutableArray{T}" />'s length must be in-between.</param>
91+
/// <param name="exceptionFactory">The delegate that creates your custom exception. <paramref name="parameter" /> and <paramref name="range" /> are passed to this delegate.</param>
92+
/// <exception cref="Exception">Your custom exception thrown when the length of <paramref name="parameter" /> is not within the specified range.</exception>
93+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
94+
[ContractAnnotation("exceptionFactory:null => halt")]
95+
public static ImmutableArray<T> MustHaveLengthIn<T>(
96+
this ImmutableArray<T> parameter,
97+
Range<int> range,
98+
Func<ImmutableArray<T>, Range<int>, Exception> exceptionFactory
99+
)
100+
{
101+
if (!range.IsValueWithinRange(parameter.Length))
102+
{
103+
Throw.CustomException(exceptionFactory, parameter, range);
104+
}
105+
106+
return parameter;
107+
}
60108
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Runtime.CompilerServices;
5+
using JetBrains.Annotations;
6+
7+
namespace Light.GuardClauses.ExceptionFactory;
8+
9+
public static partial class Throw
10+
{
11+
/// <summary>
12+
/// Throws the default <see cref="ArgumentOutOfRangeException" /> indicating that an <see cref="ImmutableArray{T}" />'s length is not within the
13+
/// given range, using the optional parameter name and message.
14+
/// </summary>
15+
[ContractAnnotation("=> halt")]
16+
[DoesNotReturn]
17+
public static void ImmutableArrayLengthNotInRange<T>(
18+
ImmutableArray<T> parameter,
19+
Range<int> range,
20+
[CallerArgumentExpression("parameter")] string? parameterName = null,
21+
string? message = null
22+
) =>
23+
throw new ArgumentOutOfRangeException(
24+
parameterName,
25+
message ??
26+
$"{parameterName ?? "The immutable array"} must have its length in between {range.CreateRangeDescriptionText("and")}, but it actually has length {parameter.Length}."
27+
);
28+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Issue 118 - MustHaveLengthIn for ImmutableArray
2+
3+
## Context
4+
5+
The .NET library Light.GuardClauses already has several assertions for collections. They often rely on `IEnumerable<T>` or `IEnumerable`. However, these assertions would result in `ImmutableArray<T>` being boxed - we want to avoid that by providing dedicated assertions for this type which avoids boxing. For this issue, we implement the `MustHaveLengthIn` assertion for `ImmutableArray<T>`.
6+
7+
## Tasks for this issue
8+
9+
- [ ] The production code should be placed in the Light.GuardClauses project. The file `Check.MustHaveLengthIn.cs` already exists in the root folder of the project - extend this existing file.
10+
- [ ] In this file, create several extension method overloads called `MustHaveLengthIn` for `ImmutableArray<T>`. It should be placed in the class `Check` which is marked as `partial`.
11+
- [ ] Each assertion in Light.GuardClauses has two overloads - the first one takes the optional `parameterName` and `message` arguments and throw the default exception. The actual exception is thrown in the `Throw` class - use the existing `Throw.ValueNotInRange` method which is located in `ExceptionFactory/Throw.Range.cs`.
12+
- [ ] The other overload takes a delegate which allows the caller to provide their own custom exceptions. Use the existing `Throw.CustomException` method and pass the delegate, the erroneous `ImmutableArray<T>` instance and the range.
13+
- [ ] The assertion takes a `Range<int>` parameter to specify the valid length range.
14+
- [ ] Use the `Length` property of `ImmutableArray<T>` instead of `Count` for performance and correctness.
15+
- [ ] Create unit tests for both overloads. The corresponding tests should be placed in Light.GuardClauses.Tests project. There is already a file 'StringAssertions/MustHaveLengthInTests.cs' but you need to create a new file 'CollectionAssertions/MustHaveLengthInTests.cs' for collection-related length tests. Please follow conventions of the existing tests (e.g. use FluentAssertions' `Should()` for assertions).
16+
17+
## Notes
18+
19+
- There are already plenty of other assertions and tests in this library. All overloads are placed in the same file in the production code project. The test projects has top-level folders for different groups of assertions, like `CollectionAssertions`, `StringAssertions`, `DateTimeAssertions` and so on. Please take a look at them to follow a similar structure and code style.
20+
- This assertion specifically targets `ImmutableArray<T>` to avoid boxing that would occur with generic `IEnumerable<T>` extensions.
21+
- Use the `Length` property instead of `Count` as this is the appropriate property for `ImmutableArray<T>`.
22+
- The assertion should verify that the `ImmutableArray<T>` length falls within the specified `Range<int>`.
23+
- If you have any questions or suggestions, please ask me about them.

0 commit comments

Comments
 (0)