Skip to content

Commit a9529b8

Browse files
authored
Merge pull request #126 from feO2x/issue-120-must-have-maximum-length-for-immutable-array
MustHaveMaximumLength for immutable array
2 parents 540cdbb + b558999 commit a9529b8

File tree

9 files changed

+355
-5
lines changed

9 files changed

+355
-5
lines changed

Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveLengthInTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,47 @@ public static void CallerArgumentExpression()
6868
act.Should().Throw<ArgumentOutOfRangeException>()
6969
.WithParameterName(nameof(testArray));
7070
}
71+
72+
[Fact]
73+
public static void DefaultImmutableArrayInRange()
74+
{
75+
var defaultArray = default(ImmutableArray<int>);
76+
77+
var result = defaultArray.MustHaveLengthIn(Range.FromInclusive(0).ToInclusive(5));
78+
79+
result.IsDefault.Should().BeTrue();
80+
}
81+
82+
[Fact]
83+
public static void DefaultImmutableArrayNotInRange()
84+
{
85+
var defaultArray = default(ImmutableArray<int>);
86+
87+
var act = () => defaultArray.MustHaveLengthIn(Range.FromInclusive(1).ToInclusive(5), nameof(defaultArray));
88+
89+
act.Should().Throw<ArgumentOutOfRangeException>()
90+
.And.Message.Should().Contain("must have its length in between 1 (inclusive) and 5 (inclusive)")
91+
.And.Contain("has no length because it is the default instance");
92+
}
93+
94+
[Fact]
95+
public static void DefaultImmutableArrayCustomException()
96+
{
97+
var defaultArray = default(ImmutableArray<int>);
98+
99+
Test.CustomException(
100+
defaultArray,
101+
Range.FromInclusive(1).ToInclusive(5),
102+
(array, r, exceptionFactory) => array.MustHaveLengthIn(r, exceptionFactory)
103+
);
104+
}
105+
106+
[Fact]
107+
public static void DefaultImmutableArrayCustomMessage() =>
108+
Test.CustomMessage<ArgumentOutOfRangeException>(
109+
message => default(ImmutableArray<int>).MustHaveLengthIn(
110+
Range.FromInclusive(1).ToInclusive(5),
111+
message: message
112+
)
113+
);
71114
}
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 MustHaveMaximumLengthTests
10+
{
11+
[Theory]
12+
[InlineData(new[] { 1, 2, 3, 4 }, 3)]
13+
[InlineData(new[] { 1, 2 }, 1)]
14+
[InlineData(new[] { 500 }, 0)]
15+
public static void ImmutableArrayMoreItems(int[] items, int length)
16+
{
17+
var immutableArray = items.ToImmutableArray();
18+
Action act = () => immutableArray.MustHaveMaximumLength(length, nameof(immutableArray));
19+
20+
var assertion = act.Should().Throw<InvalidCollectionCountException>().Which;
21+
assertion.Message.Should().Contain(
22+
$"{nameof(immutableArray)} must have at most 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" }, 2)]
30+
[InlineData(new[] { "Baz", "Qux", "Quux" }, 5)]
31+
public static void ImmutableArrayLessOrEqualItems(string[] items, int length)
32+
{
33+
var immutableArray = items.ToImmutableArray();
34+
var result = immutableArray.MustHaveMaximumLength(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.MustHaveMaximumLength(5);
43+
result.Should().Equal(emptyArray);
44+
}
45+
46+
[Theory]
47+
[InlineData(new[] { 87, 89, 99 }, 1)]
48+
[InlineData(new[] { 1, 2, 3 }, -30)]
49+
public static void ImmutableArrayCustomException(int[] items, int maximumLength)
50+
{
51+
var immutableArray = items.ToImmutableArray();
52+
53+
Action act = () => immutableArray.MustHaveMaximumLength(
54+
maximumLength,
55+
(array, length) => new ($"Custom exception for array with length {array.Length} and max {length}")
56+
);
57+
58+
act.Should().Throw<Exception>()
59+
.WithMessage($"Custom exception for array with length {immutableArray.Length} and max {maximumLength}");
60+
}
61+
62+
[Fact]
63+
public static void ImmutableArrayNoCustomExceptionThrown()
64+
{
65+
var immutableArray = new[] { "Foo", "Bar" }.ToImmutableArray();
66+
var result = immutableArray.MustHaveMaximumLength(2, (_, _) => new ());
67+
result.Should().Equal(immutableArray);
68+
}
69+
70+
[Fact]
71+
public static void ImmutableArrayCustomMessage()
72+
{
73+
var immutableArray = new[] { 1, 2, 3 }.ToImmutableArray();
74+
75+
Test.CustomMessage<InvalidCollectionCountException>(
76+
message => immutableArray.MustHaveMaximumLength(2, message: message)
77+
);
78+
}
79+
80+
[Fact]
81+
public static void ImmutableArrayCallerArgumentExpression()
82+
{
83+
var myImmutableArray = new[] { 1, 2, 3 }.ToImmutableArray();
84+
85+
var act = () => myImmutableArray.MustHaveMaximumLength(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+
DefaultImmutableArrayInstanceShouldNotThrowWhenLengthIsGreaterThanOrEqualToZero(int validLength) =>
97+
default(ImmutableArray<int>).MustHaveMaximumLength(validLength).IsDefault.Should().BeTrue();
98+
99+
[Theory]
100+
[InlineData(-1)]
101+
[InlineData(-5)]
102+
[InlineData(-12)]
103+
public static void DefaultImmutableArrayInstanceShouldNotThrowWhenLengthIsNegative(int negativeLength)
104+
{
105+
var act = () => default(ImmutableArray<int>).MustHaveMaximumLength(negativeLength);
106+
107+
act.Should().Throw<InvalidCollectionCountException>()
108+
.WithParameterName("default(ImmutableArray<int>)")
109+
.WithMessage(
110+
$"default(ImmutableArray<int>) must have at most a length of {negativeLength}, 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>).MustHaveMaximumLength(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 negativeLength)
129+
{
130+
var act = () => default(ImmutableArray<int>).MustHaveMaximumLength(
131+
negativeLength,
132+
(array, length) => new ArgumentException(
133+
$"Custom: Array length {(array.IsDefault ? 0 : array.Length)} exceeds maximum {length}"
134+
)
135+
);
136+
137+
act.Should().Throw<ArgumentException>()
138+
.WithMessage("Custom: Array length 0 exceeds maximum *");
139+
}
140+
}

Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotContainTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,51 @@ public static void ImmutableArrayCallerArgumentExpression()
140140
act.Should().Throw<ExistingItemException>()
141141
.WithParameterName(nameof(array));
142142
}
143+
144+
[Fact]
145+
public static void ImmutableArrayDefaultInstanceDoesNotContainItem()
146+
{
147+
var defaultArray = default(ImmutableArray<int>);
148+
149+
// Default instance should not throw for any item since it cannot contain anything
150+
var result = defaultArray.MustNotContain(42);
151+
152+
result.IsDefault.Should().BeTrue();
153+
}
154+
155+
[Fact]
156+
public static void ImmutableArrayDefaultInstanceCustomException()
157+
{
158+
var defaultArray = default(ImmutableArray<string>);
159+
160+
// Default instance should not throw even with custom exception factory
161+
var result = defaultArray.MustNotContain(
162+
"test",
163+
(_, _) => new InvalidOperationException("Should not be called")
164+
);
165+
166+
result.IsDefault.Should().BeTrue();
167+
}
168+
169+
[Fact]
170+
public static void ImmutableArrayDefaultInstanceCustomMessage()
171+
{
172+
var defaultArray = default(ImmutableArray<object>);
173+
174+
// Default instance should not throw even with custom message
175+
var result = defaultArray.MustNotContain(new object(), message: "Custom message");
176+
177+
result.IsDefault.Should().BeTrue();
178+
}
179+
180+
[Fact]
181+
public static void ImmutableArrayDefaultInstanceCallerArgumentExpression()
182+
{
183+
var defaultArray = default(ImmutableArray<char>);
184+
185+
// Default instance should not throw, so no exception to check parameter name
186+
var result = defaultArray.MustNotContain('x');
187+
188+
result.IsDefault.Should().BeTrue();
189+
}
143190
}

Code/Light.GuardClauses/Check.MustHaveLengthIn.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ public static ImmutableArray<T> MustHaveLengthIn<T>(
7575
string? message = null
7676
)
7777
{
78-
if (!range.IsValueWithinRange(parameter.Length))
78+
var length = parameter.IsDefault ? 0 : parameter.Length;
79+
if (!range.IsValueWithinRange(length))
7980
{
8081
Throw.ImmutableArrayLengthNotInRange(parameter, range, parameterName, message);
8182
}
@@ -98,7 +99,8 @@ public static ImmutableArray<T> MustHaveLengthIn<T>(
9899
Func<ImmutableArray<T>, Range<int>, Exception> exceptionFactory
99100
)
100101
{
101-
if (!range.IsValueWithinRange(parameter.Length))
102+
var length = parameter.IsDefault ? 0 : parameter.Length;
103+
if (!range.IsValueWithinRange(length))
102104
{
103105
Throw.CustomException(exceptionFactory, parameter, range);
104106
}
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 most 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 maximum 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 more 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> MustHaveMaximumLength<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.InvalidMaximumImmutableArrayLength(parameter, length, parameterName, message);
32+
}
33+
34+
return parameter;
35+
}
36+
37+
/// <summary>
38+
/// Ensures that the <see cref="ImmutableArray{T}" /> has at most 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 maximum 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 more 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> MustHaveMaximumLength<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/Check.MustNotContain.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ public static string MustNotContain(
207207
/// <param name="parameterName">The name of the parameter (optional).</param>
208208
/// <param name="message">The message that will be passed to the resulting exception (optional).</param>
209209
/// <exception cref="ExistingItemException">Thrown when <paramref name="parameter" /> contains <paramref name="item" />.</exception>
210+
/// <remarks>
211+
/// The default instance of <see cref="ImmutableArray{T}" /> cannot contain any items, so this method will not throw for default instances.
212+
/// </remarks>
210213
[MethodImpl(MethodImplOptions.AggressiveInlining)]
211214
public static ImmutableArray<T> MustNotContain<T>(
212215
this ImmutableArray<T> parameter,
@@ -215,7 +218,7 @@ public static ImmutableArray<T> MustNotContain<T>(
215218
string? message = null
216219
)
217220
{
218-
if (parameter.Contains(item))
221+
if (!parameter.IsDefault && parameter.Contains(item))
219222
{
220223
Throw.ExistingItem(parameter, item, parameterName, message);
221224
}
@@ -230,6 +233,9 @@ public static ImmutableArray<T> MustNotContain<T>(
230233
/// <param name="item">The item that must not be part of the <see cref="ImmutableArray{T}" />.</param>
231234
/// <param name="exceptionFactory">The delegate that creates your custom exception. <paramref name="parameter" /> and <paramref name="item" /> are passed to this delegate.</param>
232235
/// <exception cref="Exception">Your custom exception thrown when <paramref name="parameter" /> contains <paramref name="item" />.</exception>
236+
/// <remarks>
237+
/// The default instance of <see cref="ImmutableArray{T}" /> cannot contain any items, so this method will not throw for default instances.
238+
/// </remarks>
233239
[MethodImpl(MethodImplOptions.AggressiveInlining)]
234240
[ContractAnnotation("exceptionFactory:null => halt")]
235241
public static ImmutableArray<T> MustNotContain<T>(
@@ -238,7 +244,7 @@ public static ImmutableArray<T> MustNotContain<T>(
238244
Func<ImmutableArray<T>, T, Exception> exceptionFactory
239245
)
240246
{
241-
if (parameter.Contains(item))
247+
if (!parameter.IsDefault && parameter.Contains(item))
242248
{
243249
Throw.CustomException(exceptionFactory, parameter, item);
244250
}

Code/Light.GuardClauses/ExceptionFactory/Throw.ImmutableArrayLengthNotInRange.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ public static void ImmutableArrayLengthNotInRange<T>(
2323
throw new ArgumentOutOfRangeException(
2424
parameterName,
2525
message ??
26-
$"{parameterName ?? "The immutable array"} must have its length in between {range.CreateRangeDescriptionText("and")}, but it actually has length {parameter.Length}."
26+
$"{parameterName ?? "The immutable array"} must have its length in between {range.CreateRangeDescriptionText("and")}, but it actually {(parameter.IsDefault ? "has no length because it is the default instance" : $"has length {parameter.Length}")}."
2727
);
2828
}
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 more than a
13+
/// maximum number of items, using the optional parameter name and message.
14+
/// </summary>
15+
[ContractAnnotation("=> halt")]
16+
[DoesNotReturn]
17+
public static void InvalidMaximumImmutableArrayLength<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 most 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+
}

0 commit comments

Comments
 (0)