Skip to content

Commit

Permalink
Rework invalid async handling & skip unreliable test
Browse files Browse the repository at this point in the history
  • Loading branch information
DamianEdwards committed Apr 21, 2024
1 parent f2159fd commit 9933086
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 41 deletions.
76 changes: 61 additions & 15 deletions src/MiniValidation/MiniValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,19 @@ private static bool TryValidateImpl<TTarget>(TTarget target, IServiceProvider? s
{
// This is a backstop check as TryValidateImpl and the methods it calls should all be doing this check as the object
// graph is walked during validation.
ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync);
try
{
ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync);
}
catch (Exception)
{
#if NET6_0_OR_GREATER
_ = validateTask.AsTask().GetAwaiter().GetResult();
#else
_ = validateTask.GetAwaiter().GetResult();
#endif
throw;
}

#if NET6_0_OR_GREATER
isValid = validateTask.AsTask().GetAwaiter().GetResult();
Expand Down Expand Up @@ -416,7 +428,16 @@ private static async Task<bool> TryValidateImpl(
RuntimeHelpers.EnsureSufficientExecutionStack();

var validateTask = TryValidateEnumerable(target, serviceProvider, recurse, allowAsync, workingErrors, validatedObjects, validationResults, prefix, currentDepth);
ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync);

try
{
ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync);
}
catch (Exception)
{
_ = await validateTask.ConfigureAwait(false);
throw;
}

isValid = await validateTask.ConfigureAwait(false) && isValid;
}
Expand All @@ -438,7 +459,15 @@ private static async Task<bool> TryValidateImpl(
var thePrefix = $"{prefix}{propertyDetails.Name}";

var validateTask = TryValidateEnumerable(propertyValue, serviceProvider, recurse, allowAsync, workingErrors, validatedObjects, validationResults, thePrefix, currentDepth);
ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync);
try
{
ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync);
}
catch (Exception)
{
_ = await validateTask.ConfigureAwait(false);
throw;
}

isValid = await validateTask.ConfigureAwait(false) && isValid;
}
Expand All @@ -447,7 +476,15 @@ private static async Task<bool> TryValidateImpl(
var thePrefix = $"{prefix}{propertyDetails.Name}."; // <-- Note trailing '.' here

var validateTask = TryValidateImpl(propertyValue, serviceProvider, recurse, allowAsync, workingErrors, validatedObjects, validationResults, thePrefix, currentDepth + 1);
ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync);
try
{
ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync);
}
catch (Exception)
{
await validateTask.ConfigureAwait(false);
throw;
}

isValid = await validateTask.ConfigureAwait(false) && isValid;
}
Expand Down Expand Up @@ -481,7 +518,16 @@ private static async Task<bool> TryValidateImpl(
validationContext.DisplayName = validationContext.ObjectType.Name;

var validateTask = validatable.ValidateAsync(validationContext);
ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync);
try
{
ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync);
}
catch (Exception)
{
_ = await validateTask.ConfigureAwait(false);
throw;
}

var validatableResults = await validateTask.ConfigureAwait(false);
if (validatableResults is not null)
{
Expand All @@ -503,15 +549,7 @@ static string GetDisplayName(PropertyDetails property)

private static void ThrowIfAsyncNotAllowed(bool taskCompleted, bool allowAsync)
{
if (!taskCompleted)
{
ThrowIfAsyncNotAllowed(allowAsync);
}
}

private static void ThrowIfAsyncNotAllowed(bool allowAsync)
{
if (!allowAsync)
if (!allowAsync & !taskCompleted)
{
throw new InvalidOperationException($"An object in the validation graph requires async validation. Call the '{nameof(TryValidateAsync)}' method instead.");
}
Expand Down Expand Up @@ -547,8 +585,16 @@ private static async Task<bool> TryValidateEnumerable(
var itemPrefix = $"{prefix}[{index}].";

var validateTask = TryValidateImpl(item, serviceProvider, recurse, allowAsync, workingErrors, validatedObjects, validationResults, itemPrefix, currentDepth + 1);
try
{
ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync);
}
catch (Exception)
{
_ = await validateTask.ConfigureAwait(false);
throw;
}

ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync);
isValid = await validateTask.ConfigureAwait(false);

if (!isValid)
Expand Down
22 changes: 0 additions & 22 deletions src/MiniValidation/ValueTaskExtensions.cs

This file was deleted.

11 changes: 7 additions & 4 deletions tests/MiniValidation.UnitTests/Recursion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -412,18 +412,21 @@ public void Invalid_When_AsyncValidatableOnlyChild_Is_Invalid_Allowing_SyncOverA
Assert.Equal($"{nameof(TestTypeWithAsyncChild.NeedsAsync)}.{nameof(TestAsyncValidatableChildType.TwentyOrMore)}", errors.Keys.First());
}

[Fact]
[Fact(Skip = "Unreliable on CI, can reproduce on dev machine with loop count 100+")]
public void Throws_InvalidOperationException_When_Polymorphic_AsyncValidatableOnlyChild_Is_Invalid_Without_Allowing_SyncOverAsync()
{
var thingToValidate = new TestValidatableType
{
PocoChild = new TestAsyncValidatableChildType { TwentyOrMore = 12 }
};

Assert.Throws<InvalidOperationException>(() =>
for (int i = 0; i < 10000; i++)
{
var result = MiniValidator.TryValidate(thingToValidate, out var errors);
});
Assert.Throws<InvalidOperationException>(() =>
{
var result = MiniValidator.TryValidate(thingToValidate, out var errors);
});
}
}

[Fact]
Expand Down

0 comments on commit 9933086

Please sign in to comment.