Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 58 additions & 29 deletions src/GuardClauses/GuardAgainstExpressionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,74 +1,103 @@
using System;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using System.Threading;

namespace Ardalis.GuardClauses;

public static partial class GuardClauseExtensions
{
/// <summary>
/// Validates the <paramref name="input"/> using the specified <paramref name="func"/> and throws an <see cref="ArgumentException"/> or a custom <see cref="Exception" /> if it evaluates to true.
/// Validates the <paramref name="input"/> using the specified <paramref name="func"/> and throws an <see cref="ArgumentException"/>.
/// The <paramref name="func"/> should return true to indicate an invalid or undesirable state of the input.
/// If <paramref name="func"/> returns true, an <see cref="ArgumentException"/> or a custom <see cref="Exception" /> is thrown, signifying that the input is invalid.
/// If <paramref name="func"/> returns true, an <see cref="ArgumentException"/> is thrown, signifying that the input is invalid.
/// </summary>
/// <typeparam name="T">The type of the input parameter.</typeparam>
/// <param name="guardClause">The guard clause instance.</param>
/// <param name="func">The function that evaluates the input. It should return true if the input is considered invalid or in a negative state.</param>
/// <param name="input">The input to evaluate.</param>
/// <param name="message">The message to include in the exception if the input is invalid.</param>
/// <param name="parameterName">The name of the parameter to include in the thrown exception, captured automatically from the input expression.</param>
/// <param name="exceptionCreator"></param>
/// <returns>The <paramref name="input"/> if the <paramref name="func"/> evaluates to false, indicating a valid state.</returns>
/// <exception cref="ArgumentException">Thrown when the validation function returns true, indicating that the input is invalid.</exception>
/// <exception cref="Exception"></exception>
public static T Expression<T>(this IGuardClause guardClause,
Func<T, bool> func,
T input,
string message,
[CallerArgumentExpression("input")] string? parameterName = null,
Func<Exception>? exceptionCreator = null)
[CallerArgumentExpression(nameof(input))] string? parameterName = null)
where T : struct
{
if (func(input))
{
Exception? exception = exceptionCreator?.Invoke();

throw exception ?? new ArgumentException(message, parameterName!);
}
return guardClause.Expression(func, input, () => new ArgumentException(message, parameterName!));
}

return input;
/// <summary>
/// Validates the <paramref name="input"/> using the specified <paramref name="func"/> and throws a custom <see cref="Exception" /> if it evaluates to true.
/// The <paramref name="func"/> should return true to indicate an invalid or undesirable state of the input.
/// If <paramref name="func"/> returns true, a custom <see cref="Exception" /> is thrown, signifying that the input is invalid.
/// </summary>
/// <typeparam name="T">The type of the input parameter.</typeparam>
/// <param name="guardClause">The guard clause instance.</param>
/// <param name="func">The function that evaluates the input. It should return true if the input is considered invalid or in a negative state.</param>
/// <param name="input">The input to evaluate.</param>
/// <param name="createException">A factory method to create the custom exception.</param>
/// <returns>The <paramref name="input"/> if the <paramref name="func"/> evaluates to false, indicating a valid state.</returns>
/// <exception cref="Exception"></exception>
public static T Expression<T>(this IGuardClause guardClause,
Func<T, bool> func,
T input,
Func<Exception> createException)
where T : struct
{
return func(input) ? throw createException() : input;
}

/// <summary>
/// Validates the <paramref name="func"/> asynchronously and throws an <see cref="ArgumentException" /> or a custom <see cref="Exception" /> if it evaluates to false for given <paramref name="input"/>
/// Validates the <paramref name="func"/> asynchronously and throws an <see cref="ArgumentException" /> if it evaluates to false for given <paramref name="input"/>
/// The <paramref name="func"/> should return true to indicate an invalid or undesirable state.
/// If <paramref name="func"/> returns true, indicating that the input is invalid, an <see cref="ArgumentException"/> or a custom <see cref="Exception" /> is thrown.
/// If <paramref name="func"/> returns true, indicating that the input is invalid, an <see cref="ArgumentException"/> is thrown.
/// </summary>
/// <typeparam name="T">The type of the input parameter.</typeparam>
/// <param name="func">The function that evaluates the input. It should return true if the input is considered invalid or in a negative state.</param>
/// <param name="guardClause">The guard clause instance.</param>
/// <param name="func">The function that evaluates the input. It should return true if the input is considered invalid or in a negative state.</param>
/// <param name="input">The input to evaluate.</param>
/// <param name="message">The message to include in the exception if the input is invalid.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <param name="parameterName">The name of the parameter to include in the thrown exception, captured automatically from the input expression.</param>
/// <param name="exceptionCreator"></param>
/// <returns><paramref name="input"/> if the <paramref name="func"/> evaluates to true </returns>
/// <exception cref="ArgumentException">Thrown when the validation function returns true, indicating that the input is invalid.</exception>
/// <exception cref="Exception"></exception>
public static async Task<T> ExpressionAsync<T>(this IGuardClause guardClause,
Func<T, Task<bool>> func,
public static Task<T> ExpressionAsync<T>(this IGuardClause guardClause,
Func<T, CancellationToken, Task<bool>> func,
T input,
string message,
[CallerArgumentExpression("input")] string? parameterName = null,
Func<Exception>? exceptionCreator = null)
CancellationToken cancellationToken = default,
[CallerArgumentExpression(nameof(input))] string? parameterName = null)
where T : struct
{
if (await func(input))
{
Exception? exception = exceptionCreator?.Invoke();

throw exception ?? new ArgumentException(message, parameterName!);
}
return guardClause.ExpressionAsync(func, input, () => new ArgumentException(message, parameterName!), cancellationToken);
}

return input;
/// <summary>
/// Validates the <paramref name="func"/> asynchronously and throws a custom <see cref="Exception" /> if it evaluates to false for given <paramref name="input"/>
/// The <paramref name="func"/> should return true to indicate an invalid or undesirable state.
/// If <paramref name="func"/> returns true, indicating that the input is invalid, a custom <see cref="Exception" /> is thrown.
/// </summary>
/// <typeparam name="T">The type of the input parameter.</typeparam>
/// <param name="guardClause">The guard clause instance.</param>
/// <param name="func">The function that evaluates the input. It should return true if the input is considered invalid or in a negative state.</param>
/// <param name="input">The input to evaluate.</param>
/// <param name="createException">A factory method to create the custom exception.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns><paramref name="input"/> if the <paramref name="func"/> evaluates to true </returns>
/// <exception cref="ArgumentException">Thrown when the validation function returns true, indicating that the input is invalid.</exception>
/// <exception cref="Exception"></exception>
public static async Task<T> ExpressionAsync<T>(
this IGuardClause guardClause,
Func<T, CancellationToken, Task<bool>> func,
T input,
Func<Exception> createException,
CancellationToken cancellationToken = default)
where T : struct
{
return await func(input, cancellationToken).ConfigureAwait(false) ? throw createException() : input;
}
}
18 changes: 9 additions & 9 deletions test/GuardClauses.UnitTests/GuardAgainstExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,69 +26,69 @@ public static IEnumerable<object[]> GetCustomStruct()
public void GivenIntegerWhenTheExpressionEvaluatesToTrueThrowsException()
{
int testCase = 10;
Assert.Throws<ArgumentException>(() => Guard.Against.Expression((x) => x == 10, testCase, "Value cannot be 10"));
Assert.Throws<ArgumentException>(() => Guard.Against.Expression(x => x == 10, testCase, "Value cannot be 10"));
}

[Fact]
public void GivenIntegerWhenTheExpressionEvaluatesToTrueThrowsCustomExceptionWhenSupplied()
{
Exception customException = new Exception();
int testCase = 10;
Assert.Throws<Exception>(() => Guard.Against.Expression((x) => x == 10, testCase, "Value cannot be 10", exceptionCreator: () => customException));
Assert.Throws<Exception>(() => Guard.Against.Expression(x => x == 10, testCase, () => customException));
}


[Fact]
public void GivenIntegerWhenTheExpressionEvaluatesToFalseDoesNothing()
{
int testCase = 10;
Guard.Against.Expression((x) => x == 5, testCase, "Value cannot be 5");
Guard.Against.Expression(x => x == 5, testCase, "Value cannot be 5");
}

[Fact]
public void GivenDoubleWhenTheExpressionEvaluatesToTrueThrowsException()
{
double testCase = 1.1;
Assert.Throws<ArgumentException>(() => Guard.Against.Expression((x) => x == 1.1, testCase, "Value cannot be 1.1"));
Assert.Throws<ArgumentException>(() => Guard.Against.Expression(x => x == 1.1, testCase, "Value cannot be 1.1"));
}

[Fact]
public void GivenDoubleWhenTheExpressionEvaluatesToTrueThrowsCustomExceptionWhenSupplied()
{
Exception customException = new Exception();
double testCase = 1.1;
Assert.Throws<Exception>(() => Guard.Against.Expression((x) => x == 1.1, testCase, "Value cannot be 1.1", exceptionCreator: () => customException));
Assert.Throws<Exception>(() => Guard.Against.Expression(x => x == 1.1, testCase, () => customException));
}


[Fact]
public void GivenDoubleWhenTheExpressionEvaluatesToFalseDoesNothing()
{
double testCase = 1.1;
Guard.Against.Expression((x) => x == 5.0, testCase, "Value cannot be 5.0");
Guard.Against.Expression(x => x == 5.0, testCase, "Value cannot be 5.0");
}

[Theory]
[MemberData(nameof(GetCustomStruct))]
public void GivenCustomStructWhenTheExpressionEvaluatesToTrueThrowsException(CustomStruct test)
{
Assert.Throws<ArgumentException>(() => Guard.Against.Expression((x) => x.FieldName == "FieldValue", test, "FieldValue is not matching"));
Assert.Throws<ArgumentException>(() => Guard.Against.Expression(x => x.FieldName == "FieldValue", test, "FieldValue is not matching"));
}

[Theory]
[MemberData(nameof(GetCustomStruct))]
public void GivenCustomStructWhenTheExpressionEvaluatesToTrueThrowsCustomExceptionWhenSupplied(CustomStruct test)
{
Exception customException = new Exception();
Assert.Throws<Exception>(() => Guard.Against.Expression((x) => x.FieldName == "FieldValue", test, "FieldValue is not matching", exceptionCreator: () => customException));
Assert.Throws<Exception>(() => Guard.Against.Expression(x => x.FieldName == "FieldValue", test, () => customException));
}


[Theory]
[MemberData(nameof(GetCustomStruct))]
public void GivenCustomStructWhenTheExpressionEvaluatesToFalseDoesNothing(CustomStruct test)
{
Guard.Against.Expression((x) => x.FieldName == "FailThis", test, "FieldValue is not matching");
Guard.Against.Expression(x => x.FieldName == "FailThis", test, "FieldValue is not matching");
}

[Fact]
Expand Down
Loading