diff --git a/src/Sitko.FluentValidation/Graph/FluentGraphValidator.cs b/src/Sitko.FluentValidation/Graph/FluentGraphValidator.cs index 3c4276b..8606018 100644 --- a/src/Sitko.FluentValidation/Graph/FluentGraphValidator.cs +++ b/src/Sitko.FluentValidation/Graph/FluentGraphValidator.cs @@ -163,7 +163,13 @@ or char or uint or ulong or short or sbyte || var validationResult = await validator.ValidateAsync(validationContext, cancellationToken); if (!validationResult.IsValid) { - modelResult.Errors.AddRange(validationResult.Errors); + foreach (var validationFailure in validationResult.Errors) + { + if (!result.Contains(validationFailure)) + { + modelResult.Errors.Add(validationFailure); + } + } } } @@ -210,7 +216,7 @@ await TryValidateModelAsync( public record GraphValidationContextOptions { public Func? NeedToValidate { get; init; } -}; +} public abstract record GraphValidationContext(GraphValidationContextOptions Options); diff --git a/src/Sitko.FluentValidation/Graph/ModelsValidationResult.cs b/src/Sitko.FluentValidation/Graph/ModelsValidationResult.cs index 9fe8a8e..0ed6718 100644 --- a/src/Sitko.FluentValidation/Graph/ModelsValidationResult.cs +++ b/src/Sitko.FluentValidation/Graph/ModelsValidationResult.cs @@ -1,4 +1,5 @@ using System.Text; +using FluentValidation.Results; namespace Sitko.FluentValidation.Graph; @@ -25,4 +26,6 @@ public override string ToString() return errors.ToString(); } + + public bool Contains(ValidationFailure validationFailure) => Results.Any(result => result.Errors.Contains(validationFailure)); } diff --git a/tests/Sitko.FluentValidation.Tests/FluentGraphValidatorTests.cs b/tests/Sitko.FluentValidation.Tests/FluentGraphValidatorTests.cs index 59eb518..c97cfa0 100644 --- a/tests/Sitko.FluentValidation.Tests/FluentGraphValidatorTests.cs +++ b/tests/Sitko.FluentValidation.Tests/FluentGraphValidatorTests.cs @@ -1,16 +1,16 @@ -namespace Sitko.FluentValidation.Tests; - -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Core.Xunit; -using Data; using FluentAssertions; -using Graph; +using Sitko.Core.Xunit; +using Sitko.FluentValidation.Graph; +using Sitko.FluentValidation.Tests.Data; using Xunit; using Xunit.Abstractions; +namespace Sitko.FluentValidation.Tests; + public class FluentGraphValidatorTests : BaseTest { public FluentGraphValidatorTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) @@ -51,6 +51,20 @@ public async Task ValidateChild() fooResult.Errors.Should().Contain(failure => failure.PropertyName == nameof(BarModel.Val)); } + [Fact] + public async Task ValidateBoth() + { + var scope = await GetScopeAsync(); + var validator = scope.GetService(); + var bar = new BarModel { Val = 0 }; + var foo = new FooModel { Id = Guid.Empty, BarModels = new List { bar } }; + var result = await validator.TryValidateModelAsync(foo); + result.IsValid.Should().BeFalse(); + result.Results.Should().HaveCount(2); + result.Results.Where(r => r.IsValid).Should().BeEmpty(); + result.Results.SelectMany(r => r.Errors).Should().HaveCount(2); + } + [Fact] public async Task SkipChildValidation() {