Skip to content

Commit 638da53

Browse files
authored
Merge pull request #127 from feO2x/issue-121-must-have-minimum-length-for-immutable-array
MustHaveMinimumLength for immutable array
2 parents a9529b8 + cb30b20 commit 638da53

File tree

5 files changed

+253
-0
lines changed

5 files changed

+253
-0
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using FluentAssertions;
4+
using Light.GuardClauses.Exceptions;
5+
using Xunit;
6+
7+
namespace Light.GuardClauses.Tests.CollectionAssertions;
8+
9+
public static class MustHaveMinimumLengthTests
10+
{
11+
[Theory]
12+
[InlineData(new[] { 1, 2 }, 3)]
13+
[InlineData(new[] { 1 }, 2)]
14+
[InlineData(new int[] { }, 1)]
15+
public static void ImmutableArrayFewerItems(int[] items, int length)
16+
{
17+
var immutableArray = items.ToImmutableArray();
18+
Action act = () => immutableArray.MustHaveMinimumLength(length, nameof(immutableArray));
19+
20+
var assertion = act.Should().Throw<InvalidCollectionCountException>().Which;
21+
assertion.Message.Should().Contain(
22+
$"{nameof(immutableArray)} must have at least a length of {length}, but it actually has a length of {immutableArray.Length}."
23+
);
24+
assertion.ParamName.Should().BeSameAs(nameof(immutableArray));
25+
}
26+
27+
[Theory]
28+
[InlineData(new[] { "Foo" }, 1)]
29+
[InlineData(new[] { "Bar" }, 0)]
30+
[InlineData(new[] { "Baz", "Qux", "Quux" }, 2)]
31+
public static void ImmutableArrayMoreOrEqualItems(string[] items, int length)
32+
{
33+
var immutableArray = items.ToImmutableArray();
34+
var result = immutableArray.MustHaveMinimumLength(length);
35+
result.Should().Equal(immutableArray);
36+
}
37+
38+
[Fact]
39+
public static void ImmutableArrayEmpty()
40+
{
41+
var emptyArray = ImmutableArray<int>.Empty;
42+
var result = emptyArray.MustHaveMinimumLength(0);
43+
result.Should().Equal(emptyArray);
44+
}
45+
46+
[Theory]
47+
[InlineData(new[] { 87 }, 3)]
48+
[InlineData(new[] { 1, 2 }, 5)]
49+
public static void ImmutableArrayCustomException(int[] items, int minimumLength)
50+
{
51+
var immutableArray = items.ToImmutableArray();
52+
53+
Action act = () => immutableArray.MustHaveMinimumLength(
54+
minimumLength,
55+
(array, length) => new ($"Custom exception for array with length {array.Length} and min {length}")
56+
);
57+
58+
act.Should().Throw<Exception>()
59+
.WithMessage($"Custom exception for array with length {immutableArray.Length} and min {minimumLength}");
60+
}
61+
62+
[Fact]
63+
public static void ImmutableArrayNoCustomExceptionThrown()
64+
{
65+
var immutableArray = new[] { "Foo", "Bar" }.ToImmutableArray();
66+
var result = immutableArray.MustHaveMinimumLength(2, (_, _) => new ());
67+
result.Should().Equal(immutableArray);
68+
}
69+
70+
[Fact]
71+
public static void ImmutableArrayCustomMessage()
72+
{
73+
var immutableArray = new[] { 1 }.ToImmutableArray();
74+
75+
Test.CustomMessage<InvalidCollectionCountException>(
76+
message => immutableArray.MustHaveMinimumLength(3, message: message)
77+
);
78+
}
79+
80+
[Fact]
81+
public static void ImmutableArrayCallerArgumentExpression()
82+
{
83+
var myImmutableArray = new[] { 1 }.ToImmutableArray();
84+
85+
var act = () => myImmutableArray.MustHaveMinimumLength(2);
86+
87+
act.Should().Throw<InvalidCollectionCountException>()
88+
.WithParameterName(nameof(myImmutableArray));
89+
}
90+
91+
[Theory]
92+
[InlineData(0)]
93+
[InlineData(-1)]
94+
[InlineData(-5)]
95+
public static void
96+
DefaultImmutableArrayInstanceShouldNotThrowWhenLengthIsLessOrEqualToZero(int validLength) =>
97+
default(ImmutableArray<int>).MustHaveMinimumLength(validLength).IsDefault.Should().BeTrue();
98+
99+
[Theory]
100+
[InlineData(1)]
101+
[InlineData(5)]
102+
[InlineData(12)]
103+
public static void DefaultImmutableArrayInstanceShouldThrowWhenLengthIsPositive(int positiveLength)
104+
{
105+
var act = () => default(ImmutableArray<int>).MustHaveMinimumLength(positiveLength);
106+
107+
act.Should().Throw<InvalidCollectionCountException>()
108+
.WithParameterName("default(ImmutableArray<int>)")
109+
.WithMessage(
110+
$"default(ImmutableArray<int>) must have at least a length of {positiveLength}, but it actually has no length because it is the default instance.*"
111+
);
112+
}
113+
114+
[Theory]
115+
[InlineData(0)]
116+
[InlineData(-1)]
117+
[InlineData(-5)]
118+
public static void DefaultImmutableArrayInstanceCustomExceptionShouldNotThrow(int validLength)
119+
{
120+
var result = default(ImmutableArray<int>).MustHaveMinimumLength(validLength, (_, _) => new Exception());
121+
result.IsDefault.Should().BeTrue();
122+
}
123+
124+
[Theory]
125+
[InlineData(1)]
126+
[InlineData(5)]
127+
[InlineData(12)]
128+
public static void DefaultImmutableArrayInstanceCustomExceptionShouldThrow(int positiveLength)
129+
{
130+
var act = () => default(ImmutableArray<int>).MustHaveMinimumLength(
131+
positiveLength,
132+
(array, length) => new ArgumentException(
133+
$"Custom: Array length {(array.IsDefault ? 0 : array.Length)} is below minimum {length}"
134+
)
135+
);
136+
137+
act.Should().Throw<ArgumentException>()
138+
.WithMessage("Custom: Array length 0 is below minimum *");
139+
}
140+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using System.Runtime.CompilerServices;
4+
using Light.GuardClauses.ExceptionFactory;
5+
using Light.GuardClauses.Exceptions;
6+
7+
namespace Light.GuardClauses;
8+
9+
public static partial class Check
10+
{
11+
/// <summary>
12+
/// Ensures that the <see cref="ImmutableArray{T}" /> has at least the specified length, or otherwise throws an <see cref="InvalidCollectionCountException" />.
13+
/// </summary>
14+
/// <param name="parameter">The <see cref="ImmutableArray{T}" /> to be checked.</param>
15+
/// <param name="length">The minimum length the <see cref="ImmutableArray{T}" /> should have.</param>
16+
/// <param name="parameterName">The name of the parameter (optional).</param>
17+
/// <param name="message">The message that will be passed to the resulting exception (optional).</param>
18+
/// <exception cref="InvalidCollectionCountException">Thrown when <paramref name="parameter" /> has less than the specified length.</exception>
19+
/// <remarks>The default instance of <see cref="ImmutableArray{T}" /> will be treated as having length 0.</remarks>
20+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
21+
public static ImmutableArray<T> MustHaveMinimumLength<T>(
22+
this ImmutableArray<T> parameter,
23+
int length,
24+
[CallerArgumentExpression("parameter")] string? parameterName = null,
25+
string? message = null
26+
)
27+
{
28+
var parameterLength = parameter.IsDefault ? 0 : parameter.Length;
29+
if (parameterLength < length)
30+
{
31+
Throw.InvalidMinimumImmutableArrayLength(parameter, length, parameterName, message);
32+
}
33+
34+
return parameter;
35+
}
36+
37+
/// <summary>
38+
/// Ensures that the <see cref="ImmutableArray{T}" /> has at least the specified length, or otherwise throws your custom exception.
39+
/// </summary>
40+
/// <param name="parameter">The <see cref="ImmutableArray{T}" /> to be checked.</param>
41+
/// <param name="length">The minimum length the <see cref="ImmutableArray{T}" /> should have.</param>
42+
/// <param name="exceptionFactory">The delegate that creates your custom exception. <paramref name="parameter" /> and <paramref name="length" /> are passed to this delegate.</param>
43+
/// <exception cref="Exception">Your custom exception thrown when <paramref name="parameter" /> has less than the specified length.</exception>
44+
/// <remarks>The default instance of <see cref="ImmutableArray{T}" /> will be treated as having length 0.</remarks>
45+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
46+
public static ImmutableArray<T> MustHaveMinimumLength<T>(
47+
this ImmutableArray<T> parameter,
48+
int length,
49+
Func<ImmutableArray<T>, int, Exception> exceptionFactory
50+
)
51+
{
52+
var parameterLength = parameter.IsDefault ? 0 : parameter.Length;
53+
if (parameterLength < length)
54+
{
55+
Throw.CustomException(exceptionFactory, parameter, length);
56+
}
57+
58+
return parameter;
59+
}
60+
}

Code/Light.GuardClauses/ExceptionFactory/Throw.ImmutableArray.cs renamed to Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidImmutableArrayLength.cs

File renamed without changes.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.Collections.Immutable;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Runtime.CompilerServices;
4+
using JetBrains.Annotations;
5+
using Light.GuardClauses.Exceptions;
6+
7+
namespace Light.GuardClauses.ExceptionFactory;
8+
9+
public static partial class Throw
10+
{
11+
/// <summary>
12+
/// Throws the default <see cref="InvalidCollectionCountException" /> indicating that an <see cref="ImmutableArray{T}" /> has less than a
13+
/// minimum number of items, using the optional parameter name and message.
14+
/// </summary>
15+
[ContractAnnotation("=> halt")]
16+
[DoesNotReturn]
17+
public static void InvalidMinimumImmutableArrayLength<T>(
18+
ImmutableArray<T> parameter,
19+
int length,
20+
[CallerArgumentExpression("parameter")] string? parameterName = null,
21+
string? message = null
22+
)
23+
{
24+
throw new InvalidCollectionCountException(
25+
parameterName,
26+
message ??
27+
$"{parameterName ?? "The immutable array"} must have at least a length of {length}, but it actually {(parameter.IsDefault ? "has no length because it is the default instance" : $"has a length of {parameter.Length}")}."
28+
);
29+
}
30+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Issue 121 - MustHaveMinimumLength 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 `MustHaveMinimumLength` assertion for `ImmutableArray<T>`.
6+
7+
## Tasks for this issue
8+
9+
- [ ] The production code should be placed in the Light.GuardClauses project. There is no existing `Check.MustHaveMinimumLength.cs` file, but there is a `Check.MustHaveMinimumCount.cs` file. Create a new file called `Check.MustHaveMinimumLength.cs` in the root folder of the project.
10+
- [ ] In this file, create several extension method overloads called `MustHaveMinimumLength` 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.InvalidMinimumCollectionCount` method which is located in `ExceptionFactory/Throw.InvalidMinimumCollectionCount.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 minimum length.
13+
- [ ] Use the `Length` property of `ImmutableArray<T>` instead of `Count` for performance and correctness. Also decide how the default instance of an immutable array (see `ImmutableArray<T>.IsDefault`) should be handled.
14+
- [ ] Create unit tests for both overloads. The corresponding tests should be placed in Light.GuardClauses.Tests project. There is an existing file 'CollectionAssertions/MustHaveMinimumCountTests.cs' but you need to create a new file 'CollectionAssertions/MustHaveMinimumLengthTests.cs' for length-related tests. Please follow conventions of the existing tests (e.g. use FluentAssertions' `Should()` for assertions).
15+
16+
## Notes
17+
18+
- 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.
19+
- Especially take a look at the existing `Check.MustHaveMaximumLength.cs` file and the referenced `Throw.InvalidMaximumImmutableArrayLength` method - you basically have to implement the "opposite" of it.
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>` has at least the specified minimum length.
23+
- If you have any questions or suggestions, please ask me about them.

0 commit comments

Comments
 (0)