Skip to content

Commit

Permalink
Adds NullOrOutOfRange clauses, adding support for nullable classes/…
Browse files Browse the repository at this point in the history
…structs to `OutOfRange` (#143)
  • Loading branch information
rafaelsc committed Jul 31, 2023
1 parent f9b6c19 commit 4d8231a
Show file tree
Hide file tree
Showing 16 changed files with 558 additions and 57 deletions.
114 changes: 108 additions & 6 deletions src/GuardClauses/GuardAgainstOutOfRangeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace Ardalis.GuardClauses;
Expand Down Expand Up @@ -88,7 +89,11 @@ public static T EnumOutOfRange<T>(this IGuardClause guardClause,
/// <returns><paramref name="input" /> if any item is not out of range.</returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static IEnumerable<T> OutOfRange<T>(this IGuardClause guardClause, IEnumerable<T> input, string parameterName, T rangeFrom, T rangeTo, string? message = null) where T : IComparable, IComparable<T>
public static IEnumerable<T> OutOfRange<T>(this IGuardClause guardClause,
IEnumerable<T> input,
string parameterName,
T rangeFrom, T rangeTo,
string? message = null) where T : IComparable, IComparable<T>
{
if (rangeFrom.CompareTo(rangeTo) > 0)
{
Expand All @@ -106,6 +111,34 @@ public static IEnumerable<T> OutOfRange<T>(this IGuardClause guardClause, IEnume

return input;
}

/// <summary>
/// Throws an <see cref="ArgumentNullException" /> if <paramref name="input" /> is null.
/// Throws an <see cref="ArgumentOutOfRangeException" /> if <paramref name="input" /> is not in the range of valid SqlDateTime values.
/// </summary>
/// <param name="guardClause"></param>
/// <param name="input"></param>
/// <param name="parameterName"></param>
/// <param name="message">Optional. Custom error message</param>
/// <returns><paramref name="input" /> if the value is in the range of valid SqlDateTime values.</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
#if NETSTANDARD || NETFRAMEWORK
public static DateTime NullOrOutOfSQLDateRange(this IGuardClause guardClause,
[NotNull][ValidatedNotNull] DateTime? input,
string parameterName,
string? message = null)
#else
public static DateTime NullOrOutOfSQLDateRange(this IGuardClause guardClause,
[NotNull][ValidatedNotNull] DateTime? input,
[CallerArgumentExpression("input")] string parameterName = null,

Check warning on line 134 in src/GuardClauses/GuardAgainstOutOfRangeExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 134 in src/GuardClauses/GuardAgainstOutOfRangeExtensions.cs

View workflow job for this annotation

GitHub Actions / list Ardalis.GuardClauses on nuget.org

Cannot convert null literal to non-nullable reference type.

Check warning on line 134 in src/GuardClauses/GuardAgainstOutOfRangeExtensions.cs

View workflow job for this annotation

GitHub Actions / list Ardalis.GuardClauses on nuget.org

Cannot convert null literal to non-nullable reference type.
string? message = null)
#endif
{
guardClause.Null(input, nameof(input));
return OutOfSQLDateRange(guardClause, input.Value, parameterName, message);
}

/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException" /> if <paramref name="input" /> is not in the range of valid SqlDateTime values.
/// </summary>
Expand All @@ -131,7 +164,7 @@ public static DateTime OutOfSQLDateRange(this IGuardClause guardClause,
const long sqlMinDateTicks = 552877920000000000;
const long sqlMaxDateTicks = 3155378975999970000;

return OutOfRange<DateTime>(guardClause, input, parameterName!, new DateTime(sqlMinDateTicks), new DateTime(sqlMaxDateTicks), message);
return NullOrOutOfRangeInternal<DateTime>(guardClause, input, parameterName, new DateTime(sqlMinDateTicks), new DateTime(sqlMaxDateTicks), message);
}

/// <summary>
Expand All @@ -145,12 +178,82 @@ public static DateTime OutOfSQLDateRange(this IGuardClause guardClause,
/// <param name="message">Optional. Custom error message</param>
/// <returns><paramref name="input" /> if the value is not out of range.</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static T OutOfRange<T>(this IGuardClause guardClause, T input,
public static T OutOfRange<T>(this IGuardClause guardClause,
T input,
string parameterName,
T rangeFrom,
T rangeTo,
[NotNull][ValidatedNotNull] T rangeFrom,
[NotNull][ValidatedNotNull] T rangeTo,
string? message = null) where T : IComparable, IComparable<T>
{
return NullOrOutOfRangeInternal<T>(guardClause, input, parameterName, rangeFrom, rangeTo, message);
}


/// <summary>
/// Throws an <see cref="ArgumentNullException" /> if <paramref name="input" /> is null.
/// Throws an <see cref="ArgumentOutOfRangeException" /> if <paramref name="input"/> is less than <paramref name="rangeFrom"/> or greater than <paramref name="rangeTo"/>.
/// </summary>
/// <param name="guardClause"></param>
/// <param name="input"></param>
/// <param name="parameterName"></param>
/// <param name="rangeFrom"></param>
/// <param name="rangeTo"></param>
/// <param name="message">Optional. Custom error message</param>
/// <returns><paramref name="input" /> if the value is not not null or out of range.</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static T NullOrOutOfRange<T>(this IGuardClause guardClause,
[NotNull][ValidatedNotNull] T? input,
string parameterName,
[NotNull][ValidatedNotNull] T rangeFrom,
[NotNull][ValidatedNotNull] T rangeTo,
string? message = null) where T : IComparable<T>
{
guardClause.Null(input, nameof(input));
return NullOrOutOfRangeInternal(guardClause, input, parameterName, rangeFrom, rangeTo, message);
}

/// <summary>
/// Throws an <see cref="ArgumentNullException" /> if <paramref name="input" /> is null.
/// Throws an <see cref="ArgumentOutOfRangeException" /> if <paramref name="input"/> is less than <paramref name="rangeFrom"/> or greater than <paramref name="rangeTo"/>.
/// </summary>
/// <param name="guardClause"></param>
/// <param name="input"></param>
/// <param name="parameterName"></param>
/// <param name="rangeFrom"></param>
/// <param name="rangeTo"></param>
/// <param name="message">Optional. Custom error message</param>
/// <returns><paramref name="input" /> if the value is not not null or out of range.</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static T NullOrOutOfRange<T>(this IGuardClause guardClause,
[NotNull][ValidatedNotNull] T? input,
string parameterName,
[NotNull][ValidatedNotNull] T rangeFrom,
[NotNull][ValidatedNotNull] T rangeTo,
string? message = null) where T : struct, IComparable<T>
{
guardClause.Null(input, nameof(input));
return NullOrOutOfRangeInternal<T>(guardClause, input.Value, parameterName, rangeFrom, rangeTo, message);
}

/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
private static T NullOrOutOfRangeInternal<T>(this IGuardClause guardClause,
[NotNull][ValidatedNotNull] T? input,
string? parameterName,
[NotNull][ValidatedNotNull] T? rangeFrom,
[NotNull][ValidatedNotNull] T? rangeTo,
string? message = null) where T : IComparable<T>?
{
Guard.Against.Null(input, nameof(input));
Guard.Against.Null(parameterName, nameof(parameterName));
Guard.Against.Null(rangeFrom, nameof(rangeFrom));
Guard.Against.Null(rangeTo, nameof(rangeTo));

if (rangeFrom.CompareTo(rangeTo) > 0)
{
throw new ArgumentException(message ?? $"{nameof(rangeFrom)} should be less or equal than {nameof(rangeTo)}", parameterName);
Expand All @@ -167,5 +270,4 @@ public static T OutOfRange<T>(this IGuardClause guardClause, T input,

return input;
}

}
12 changes: 6 additions & 6 deletions test/GuardClauses.UnitTests/GuardAgainstNullOrInvalidInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ public IEnumerator<object[]> GetEnumerator()
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class ArgumentNullExceptionClassData : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
public class ArgumentNullExceptionClassData : IEnumerable<object?[]>
{
yield return new object[] { null!, (Func<string, bool>)(x => x.Length > 10) };
public IEnumerator<object?[]> GetEnumerator()
{
yield return new object?[] { null, (Func<string, bool>)(x => x.Length > 10) };
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class ArgumentExceptionClassData : IEnumerable<object[]>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Ardalis.GuardClauses;
using Xunit;

namespace GuardClauses.UnitTests
{
/// <summary>
/// Every type that implements IComparable and IComparable<T> can use OutOfRange.
/// Here for example tuples are used.
/// </summary>
public class GuardAgainstNullOrOutOfRangeForClassIComparable
{
private class TestObj : IComparable<TestObj?>, IEquatable<TestObj?>
{
private readonly int _internalValue;

public TestObj(int internalValue)
{
_internalValue = internalValue;
}

public int CompareTo([AllowNull] TestObj? other) => _internalValue.CompareTo(other?._internalValue);


public override bool Equals(object? obj)
{
if (obj is null) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof(TestObj)) return false;
return Equals((TestObj)obj);
}
public bool Equals([AllowNull] TestObj? other) => ReferenceEquals(this, other) || _internalValue == other?._internalValue;

public override int GetHashCode() => _internalValue.GetHashCode();
}

[Fact]
public void DoesNothingGivenInRangeValue()
{
Guard.Against.NullOrOutOfRange(new TestObj(1), "index", new TestObj(1), new TestObj(1));
Guard.Against.NullOrOutOfRange(new TestObj(2), "index", new TestObj(1), new TestObj(3));
Guard.Against.NullOrOutOfRange(new TestObj(3), "index", new TestObj(1), new TestObj(3));
}

[Fact]
public void ThrowsGivenOutOfRangeValue()
{
Assert.Throws<ArgumentOutOfRangeException>(() => Guard.Against.NullOrOutOfRange(new TestObj(-1), "index", new TestObj(1), new TestObj(3)));
Assert.Throws<ArgumentOutOfRangeException>(() => Guard.Against.NullOrOutOfRange(new TestObj(0), "index", new TestObj(1), new TestObj(3)));
Assert.Throws<ArgumentOutOfRangeException>(() => Guard.Against.NullOrOutOfRange(new TestObj(4), "index", new TestObj(1), new TestObj(3)));
}

[Fact]
public void ThrowsGivenInvalidArgumentValue()
{
Assert.Throws<ArgumentException>(() => Guard.Against.NullOrOutOfRange(new TestObj(-1), "index", new TestObj(3), new TestObj(1)));
Assert.Throws<ArgumentException>(() => Guard.Against.NullOrOutOfRange(new TestObj(0), "index", new TestObj(3), new TestObj(1)));
Assert.Throws<ArgumentException>(() => Guard.Against.NullOrOutOfRange(new TestObj(4), "index", new TestObj(3), new TestObj(1)));
}

[Fact]
public void ThrowsGivenInvalidNullArgumentValue()
{
#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type.

Assert.Throws<ArgumentNullException>(() => Guard.Against.NullOrOutOfRange(null, "index", new TestObj(3), new TestObj(1)));
Assert.Throws<ArgumentNullException>(() => Guard.Against.NullOrOutOfRange(new TestObj(0), "index", null, new TestObj(1)));
Assert.Throws<ArgumentNullException>(() => Guard.Against.NullOrOutOfRange(new TestObj(4), "index", new TestObj(3), null));

#pragma warning restore CS8631
}

[Fact]
public void ReturnsExpectedValueGivenInRangeValue()
{
Assert.Equal(new TestObj(1), Guard.Against.NullOrOutOfRange(new TestObj(1), "index", new TestObj(1), new TestObj(1)));
Assert.Equal(new TestObj(1), Guard.Against.NullOrOutOfRange(new TestObj(1), "index", new TestObj(1), new TestObj(3)));
Assert.Equal(new TestObj(2), Guard.Against.NullOrOutOfRange(new TestObj(2), "index", new TestObj(1), new TestObj(3)));
Assert.Equal(new TestObj(3), Guard.Against.NullOrOutOfRange(new TestObj(3), "index", new TestObj(1), new TestObj(3)));
}
}
}
58 changes: 58 additions & 0 deletions test/GuardClauses.UnitTests/GuardAgainstNullOrOutOfRangeForInt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Ardalis.GuardClauses;
using System;
using Xunit;

namespace GuardClauses.UnitTests
{
public class GuardAgainstNullOrOutOfRangeForInt
{
[Theory]
[InlineData(1, 1, 1)]
[InlineData(1, 1, 3)]
[InlineData(2, 1, 3)]
[InlineData(3, 1, 3)]
public void DoesNothingGivenInRangeValue(int input, int rangeFrom, int rangeTo)
{
Guard.Against.NullOrOutOfRange(input, "index", rangeFrom, rangeTo);
}

[Theory]
[InlineData(-1, 1, 3)]
[InlineData(0, 1, 3)]
[InlineData(4, 1, 3)]
public void ThrowsGivenOutOfRangeValue(int input, int rangeFrom, int rangeTo)
{
Assert.Throws<ArgumentOutOfRangeException>(() => Guard.Against.NullOrOutOfRange(input, "index", rangeFrom, rangeTo));
}

[Theory]
[InlineData(-1, 3, 1)]
[InlineData(0, 3, 1)]
[InlineData(4, 3, 1)]
public void ThrowsGivenInvalidArgumentValue(int input, int rangeFrom, int rangeTo)
{
Assert.Throws<ArgumentException>(() => Guard.Against.NullOrOutOfRange(input, "index", rangeFrom, rangeTo));
}

[Theory]
[InlineData(1, 1, 1, 1)]
[InlineData(1, 1, 3, 1)]
[InlineData(2, 1, 3, 2)]
[InlineData(3, 1, 3, 3)]
public void ReturnsExpectedValueGivenInRangeValue(int input, int rangeFrom, int rangeTo, int expected)
{
Assert.Equal(expected, Guard.Against.NullOrOutOfRange(input, "index", rangeFrom, rangeTo));
}

[Theory]
[InlineData(null, "Input parameterName was out of range (Parameter 'parameterName')")]
[InlineData("Int range", "Int range (Parameter 'parameterName')")]
public void ErrorMessageMatchesExpected(string customMessage, string expectedMessage)
{
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => Guard.Against.NullOrOutOfRange(3, "parameterName", 0, 1, customMessage));
Assert.NotNull(exception);
Assert.NotNull(exception.Message);
Assert.Equal(expectedMessage, exception.Message);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Ardalis.GuardClauses;
using System;
using Xunit;

namespace GuardClauses.UnitTests
{
public class GuardAgainstNullOrOutOfRangeForNullableInt
{
[Theory]
[InlineData(1, 1, 1)]
[InlineData(1, 1, 3)]
[InlineData(2, 1, 3)]
[InlineData(3, 1, 3)]
public void DoesNothingGivenInRangeValue(int? input, int rangeFrom, int rangeTo)
{
Guard.Against.NullOrOutOfRange(input, "index", rangeFrom, rangeTo);
}

[Theory]
[InlineData(-1, 1, 3)]
[InlineData(0, 1, 3)]
[InlineData(4, 1, 3)]
public void ThrowsGivenOutOfRangeValue(int? input, int rangeFrom, int rangeTo)
{
Assert.Throws<ArgumentOutOfRangeException>(() => Guard.Against.NullOrOutOfRange(input, "index", rangeFrom, rangeTo));
}

[Theory]
[InlineData(-1, 3, 1)]
[InlineData(0, 3, 1)]
[InlineData(4, 3, 1)]
public void ThrowsGivenInvalidArgumentValue(int? input, int rangeFrom, int rangeTo)
{
Assert.Throws<ArgumentException>(() => Guard.Against.NullOrOutOfRange(input, "index", rangeFrom, rangeTo));
}

[Theory]
[InlineData(1, 1, 1, 1)]
[InlineData(1, 1, 3, 1)]
[InlineData(2, 1, 3, 2)]
[InlineData(3, 1, 3, 3)]
public void ReturnsExpectedValueGivenInRangeValue(int? input, int rangeFrom, int rangeTo, int expected)
{
Assert.Equal(expected, Guard.Against.NullOrOutOfRange(input, "index", rangeFrom, rangeTo));
}

[Fact]
public void ThrowsGivenInvalidNullArgumentValue()
{
Assert.Throws<ArgumentNullException>(() => Guard.Against.NullOrOutOfRange(null, "index", -10, 10));
}
}
}
Loading

0 comments on commit 4d8231a

Please sign in to comment.