From 99ee4ef53f1660f6fa70ba9ba6302e978f7c9323 Mon Sep 17 00:00:00 2001 From: Robert Andersson Date: Tue, 27 Nov 2018 22:53:57 +0100 Subject: [PATCH] Hide validator behind validator interface --- src/Lemonad.ErrorHandling/IValidator.cs | 49 ++++++++++ .../Internal/Validator.cs | 43 ++++++++ src/Lemonad.ErrorHandling/Validator.cs | 97 +++---------------- .../Validator.Tests/ValidateTests.cs | 96 +++++++++++++++--- 4 files changed, 192 insertions(+), 93 deletions(-) create mode 100644 src/Lemonad.ErrorHandling/IValidator.cs create mode 100644 src/Lemonad.ErrorHandling/Internal/Validator.cs diff --git a/src/Lemonad.ErrorHandling/IValidator.cs b/src/Lemonad.ErrorHandling/IValidator.cs new file mode 100644 index 00000000..eaf1ddc7 --- /dev/null +++ b/src/Lemonad.ErrorHandling/IValidator.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using Lemonad.ErrorHandling.Internal; + +namespace Lemonad.ErrorHandling { + /// + /// An collection of based on validations of . + /// + /// + /// The type of the validation candidate. + /// + /// + /// The type of the the error type. + /// + public interface IValidator : IReadOnlyCollection { + /// + /// Validate with an function and set the failure type in . + /// + /// + /// A function to test for a condition. + /// + /// + /// A function to set the error if the predicate function would return false. + /// + /// + /// An . + /// + IValidator Validate(Func predicate, Func errorSelector); + + /// + /// Validate with an function and set the failure type with . + /// + /// + /// A function to test for a condition. + /// + /// + /// The error value. + /// + /// + /// An . + /// + IValidator Validate(Func predicate, TError error); + + /// + /// Gets the . + /// + IResult> Result { get; } + } +} \ No newline at end of file diff --git a/src/Lemonad.ErrorHandling/Internal/Validator.cs b/src/Lemonad.ErrorHandling/Internal/Validator.cs new file mode 100644 index 00000000..d9565903 --- /dev/null +++ b/src/Lemonad.ErrorHandling/Internal/Validator.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Lemonad.ErrorHandling.Internal { + internal readonly struct Validator : IValidator { + private readonly T _candidate; + private readonly Queue _errors; + + public IResult> Result { get; } + + internal Validator(T candidate) { + _candidate = candidate; + _errors = new Queue(); + Result = ErrorHandling.Result.Value>(_candidate); + } + + private Validator(in Queue errors, in T candidate) { + _errors = new Queue(errors); + _candidate = candidate; + Result = _errors.Count > 0 + ? ErrorHandling.Result.Error>(_errors) + : ErrorHandling.Result.Value>(_candidate); + } + + public IEnumerator GetEnumerator() => _errors.GetEnumerator(); + + public IValidator Validate(Func predicate, Func errorSelector) { + if (predicate == null) throw new ArgumentException(nameof(predicate)); + if (errorSelector == null) throw new ArgumentException(nameof(errorSelector)); + var result = _candidate.ToResult(predicate, errorSelector); + // This refers to the initial queue. + if (result.Either.HasError) _errors.Enqueue(result.Either.Error); + return new Validator(_errors, _candidate); + } + + public IValidator Validate(Func predicate, TError error) => Validate(predicate, _ => error); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int Count => _errors.Count; + } +} \ No newline at end of file diff --git a/src/Lemonad.ErrorHandling/Validator.cs b/src/Lemonad.ErrorHandling/Validator.cs index 1c1b0ad1..e62704b9 100644 --- a/src/Lemonad.ErrorHandling/Validator.cs +++ b/src/Lemonad.ErrorHandling/Validator.cs @@ -1,87 +1,20 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System; using Lemonad.ErrorHandling.Internal; namespace Lemonad.ErrorHandling { - /// - /// An collection of based on validations of . - /// - /// - /// The type of the validation candidate. - /// - /// - /// The type of the the error type. - /// - public readonly struct Validator : IReadOnlyCollection { - private readonly T _candidate; - private readonly Queue _errors; - - /// - /// Convert to a . - /// - public IResult> Result { - get { - var candidate = _candidate; - return _errors.ToResultError>(x => x.Count > 0, _ => candidate); - } - } - - /// - /// Creates an instance of . - /// - /// - /// The validation . - /// - public Validator(T candidate) { - _candidate = candidate; - _errors = new Queue(); - } - - private Validator(in Queue errors, in T candidate) { - _errors = errors; - _candidate = candidate; - } - - /// - public IEnumerator GetEnumerator() => _errors.GetEnumerator(); - - /// - /// Validate with an function and set the failure type in . - /// - /// - /// A function to test for a condition. - /// - /// - /// A function to set the error if the predicate function would return false. - /// - /// - /// An . - /// - public Validator Validate(Func predicate, Func errorSelector) { - var result = _candidate.ToResult(predicate, _ => errorSelector()); - if (result.Either.HasError) _errors.Enqueue(result.Either.Error); - return new Validator(_errors, _candidate); - } - - /// - /// Validate with an function and set the failure type with . - /// - /// - /// A function to test for a condition. - /// - /// - /// The error value. - /// - /// - /// An . - /// - public Validator Validate(Func predicate, TError error) => Validate(predicate, () => error); - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public int Count => _errors.Count; + public static class Validator { + public static IValidator Value(T value) => new Validator(value); + + public static IValidator Value( + T value, + Func predicate, + Func errorSelector + ) => new Validator(value).Validate(predicate, errorSelector); + + public static IValidator Value( + T value, + Func predicate, + TError errorSelector + ) => new Validator(value).Validate(predicate, errorSelector); } } \ No newline at end of file diff --git a/test/Lemonad.ErrorHandling.Test/Validator.Tests/ValidateTests.cs b/test/Lemonad.ErrorHandling.Test/Validator.Tests/ValidateTests.cs index 21be46d0..fccc8060 100644 --- a/test/Lemonad.ErrorHandling.Test/Validator.Tests/ValidateTests.cs +++ b/test/Lemonad.ErrorHandling.Test/Validator.Tests/ValidateTests.cs @@ -6,14 +6,14 @@ public class ValidateTests { [Fact] public void Single_False_Validation() { const string error = "Not dividable by 2"; - var validator = new Validator(1).Validate(i => i % 2 == 0, error).ToList(); + var validator = ErrorHandling.Validator.Value(1).Validate(i => i % 2 == 0, error).ToList(); Assert.Single(validator, error); } [Fact] public void Single_True_Validation() { const string error = "Not dividable by 2"; - var validator = new Validator(2).Validate(i => i % 2 == 0, error).ToList(); + var validator = ErrorHandling.Validator.Value(2).Validate(i => i % 2 == 0, error).ToList(); Assert.Empty(validator); } @@ -21,20 +21,34 @@ public void Single_True_Validation() { public void Double_True_Validation() { const string error1 = "Not dividable by 2"; const string error2 = "Is equal to 2."; - var validator = new Validator(2) + var validator = ErrorHandling.Validator.Value(2) .Validate(i => i % 2 == 0, error1) - .Validate(i => i == 2, () => error2) + .Validate(i => i == 2, x => error2) .ToList(); Assert.Empty(validator); } + [Fact] + public void Double_True_Validation_Verify_States() { + const string error1 = "Not dividable by 2"; + const string error2 = "Is equal to 2."; + var state0 = ErrorHandling.Validator.Value(2); + Assert.Empty(state0); + var state1 = state0.Validate(i => i % 2 == 0, error1); + Assert.Empty(state1); + var state2 = state1.Validate(i => i == 2, x => error2); + Assert.Empty(state2); + var validator = state2.ToList(); + Assert.Empty(validator); + } + [Fact] public void Double_False_Validation() { const string error1 = "Is not equal to 0"; const string error2 = "Not dividable by 2"; - var validator = new Validator(1) - .Validate(i => i == 0, () => error1) - .Validate(i => i % 2 == 0, () => error2) + var validator = ErrorHandling.Validator.Value(1) + .Validate(i => i == 0, x => error1) + .Validate(i => i % 2 == 0, x => error2) .ToList(); Assert.Equal(2, validator.Count); Assert.Equal(error1, validator[0]); @@ -42,14 +56,74 @@ public void Double_False_Validation() { } [Fact] - public void Single_True_Validation_And_Single_False_Validation() { + public void Double_False_Validation_Verify_States() { + const string error1 = "Is not equal to 0"; + const string error2 = "Not dividable by 2"; + var state0 = ErrorHandling.Validator.Value(1); + Assert.Empty(state0); + var state1 = state0.Validate(i => i == 0, x => error1); + Assert.Single(state1); + var state2 = state1.Validate(i => i % 2 == 0, x => error2); + Assert.Equal(2, state2.Count); + var validator = state2.ToList(); + Assert.Equal(2, validator.Count); + Assert.Equal(error1, validator[0]); + Assert.Equal(error2, validator[1]); + } + + [Fact] + public void First_False_Validation_And_Second_True_Validation_Verify_States() { + const string error1 = "Is not equal to 0"; + const string error2 = "Not dividable by 2"; + var state0 = ErrorHandling.Validator.Value(2); + var state1 = state0.Validate(i => i == 0, x => error1); + var state2 = state1.Validate(i => i % 2 == 0, x => error2); + // These errors occur since the collection in `state2` references the collection in `state0`. + // So all errors in `state2` are found in `state0` therefor. + Assert.Empty(state0); + Assert.Single(state1); + Assert.Single(state2); + } + + [Fact] + public void First_True_Validation_And_Second_False_Validation_Verify_States() { const string error1 = "Is not equal to 0"; const string error2 = "Not dividable by 2"; - var validator = new Validator(2) - .Validate(i => i == 0, () => error1) - .Validate(i => i % 2 == 0, () => error2) + var state0 = ErrorHandling.Validator.Value(2); + var state1 = state0.Validate(i => i % 2 == 0, x => error2); + var state2 = state1.Validate(i => i == 0, x => error1); + Assert.Empty(state0); + Assert.Empty(state1); + Assert.Single(state2); + } + + [Fact] + public void First_False_Validation_And_Second_True_Validation() { + const string error1 = "Is not equal to 0"; + const string error2 = "Not dividable by 2"; + var validator = ErrorHandling.Validator.Value(2) + .Validate(i => i == 0, x => error1) + .Validate(i => i % 2 == 0, x => error2) .ToList(); Assert.Single(validator); } + + [Fact] + public void First_True_Validation_And_Second_False_Validation() { + const string error1 = "Is not equal to 0"; + const string error2 = "Not dividable by 2"; + var validator = ErrorHandling.Validator.Value(2) + .Validate(i => i % 2 == 0, x => error2) + .Validate(i => i == 0, x => error1) + .ToList(); + Assert.Single(validator); + } + + [Fact] + public void Result_Does_Not_Create_New_Instance_On_Get() { + var validator = ErrorHandling.Validator.Value(2); + Assert.Equal(validator.Result, validator.Result); + Assert.StrictEqual(validator.Result, validator.Result); + } } } \ No newline at end of file