Skip to content

Commit

Permalink
Hide validator behind validator interface
Browse files Browse the repository at this point in the history
  • Loading branch information
inputfalken committed Nov 27, 2018
1 parent 0335677 commit 3d41cb1
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 87 deletions.
49 changes: 49 additions & 0 deletions src/Lemonad.ErrorHandling/IValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using Lemonad.ErrorHandling.Internal;

namespace Lemonad.ErrorHandling {
/// <summary>
/// An <typeparamref name="TError"/> collection of <typeparamref name="TError"/> based on validations of <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">
/// The type of the validation candidate.
/// </typeparam>
/// <typeparam name="TError">
/// The type of the the error type.
/// </typeparam>
public interface IValidator<out T, TError> : IReadOnlyCollection<TError> {
/// <summary>
/// Validate <typeparamref name="T"/> with an <paramref name="predicate"/> function and set the failure type in <paramref name="errorSelector"/>.
/// </summary>
/// <param name="predicate">
/// A function to test <typeparamref name="T"/> for a condition.
/// </param>
/// <param name="errorSelector">
/// A function to set the error if the predicate function would return false.
/// </param>
/// <returns>
/// An <see cref="Validator{T,TError}"/>.
/// </returns>
IValidator<T, TError> Validate(Func<T, bool> predicate, Func<TError> errorSelector);

/// <summary>
/// Validate <typeparamref name="T"/> with an <paramref name="predicate"/> function and set the failure type with <paramref name="error"/>.
/// </summary>
/// <param name="predicate">
/// A function to test <typeparamref name="T"/> for a condition.
/// </param>
/// <param name="error">
/// The error value.
/// </param>
/// <returns>
/// An <see cref="Validator{T,TError}"/>.
/// </returns>
IValidator<T, TError> Validate(Func<T, bool> predicate, TError error);

/// <summary>
/// Gets the <see cref="IResult{T,TError}"/>.
/// </summary>
IResult<T, IReadOnlyCollection<TError>> Result { get; }
}
}
42 changes: 42 additions & 0 deletions src/Lemonad.ErrorHandling/Internal/Validator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Collections;
using System.Collections.Generic;

namespace Lemonad.ErrorHandling.Internal {
internal readonly struct Validator<T, TError> : IValidator<T, TError> {
private readonly T _candidate;
private readonly Queue<TError> _errors;

public IResult<T, IReadOnlyCollection<TError>> Result {
get {
var candidate = _candidate;
return _errors.ToResultError<T, IReadOnlyCollection<TError>>(x => x.Count > 0, _ => candidate);
}
}

public Validator(T candidate) {
_candidate = candidate;
_errors = new Queue<TError>();
}

private Validator(in Queue<TError> errors, in T candidate) {
_errors = errors;
_candidate = candidate;
}

public IEnumerator<TError> GetEnumerator() => _errors.GetEnumerator();

public IValidator<T, TError> Validate(Func<T, bool> predicate, Func<TError> errorSelector) {
var result = _candidate.ToResult(predicate, _ => errorSelector());
if (result.Either.HasError) _errors.Enqueue(result.Either.Error);
return new Validator<T, TError>(_errors, _candidate);
}

public IValidator<T, TError> Validate(Func<T, bool> predicate, TError error) =>
Validate(predicate, () => error);

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public int Count => _errors.Count;
}
}
97 changes: 15 additions & 82 deletions src/Lemonad.ErrorHandling/Validator.cs
Original file line number Diff line number Diff line change
@@ -1,87 +1,20 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System;
using Lemonad.ErrorHandling.Internal;

namespace Lemonad.ErrorHandling {
/// <summary>
/// An <typeparamref name="TError"/> collection of <typeparamref name="TError"/> based on validations of <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">
/// The type of the validation candidate.
/// </typeparam>
/// <typeparam name="TError">
/// The type of the the error type.
/// </typeparam>
public readonly struct Validator<T, TError> : IReadOnlyCollection<TError> {
private readonly T _candidate;
private readonly Queue<TError> _errors;

/// <summary>
/// Convert to a <see cref="Result{T,TError}"/>.
/// </summary>
public IResult<T, IReadOnlyCollection<TError>> Result {
get {
var candidate = _candidate;
return _errors.ToResultError<T, IReadOnlyCollection<TError>>(x => x.Count > 0, _ => candidate);
}
}

/// <summary>
/// Creates an instance of <see cref="Validator{T,TError}"/>.
/// </summary>
/// <param name="candidate">
/// The validation <paramref name="candidate"/>.
/// </param>
public Validator(T candidate) {
_candidate = candidate;
_errors = new Queue<TError>();
}

private Validator(in Queue<TError> errors, in T candidate) {
_errors = errors;
_candidate = candidate;
}

/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
public IEnumerator<TError> GetEnumerator() => _errors.GetEnumerator();

/// <summary>
/// Validate <typeparamref name="T"/> with an <paramref name="predicate"/> function and set the failure type in <paramref name="errorSelector"/>.
/// </summary>
/// <param name="predicate">
/// A function to test <typeparamref name="T"/> for a condition.
/// </param>
/// <param name="errorSelector">
/// A function to set the error if the predicate function would return false.
/// </param>
/// <returns>
/// An <see cref="Validator{T,TError}"/>.
/// </returns>
public Validator<T, TError> Validate(Func<T, bool> predicate, Func<TError> errorSelector) {
var result = _candidate.ToResult(predicate, _ => errorSelector());
if (result.Either.HasError) _errors.Enqueue(result.Either.Error);
return new Validator<T, TError>(_errors, _candidate);
}

/// <summary>
/// Validate <typeparamref name="T"/> with an <paramref name="predicate"/> function and set the failure type with <paramref name="error"/>.
/// </summary>
/// <param name="predicate">
/// A function to test <typeparamref name="T"/> for a condition.
/// </param>
/// <param name="error">
/// The error value.
/// </param>
/// <returns>
/// An <see cref="Validator{T,TError}"/>.
/// </returns>
public Validator<T, TError> Validate(Func<T, bool> predicate, TError error) => Validate(predicate, () => error);

/// <inheritdoc cref="IEnumerable.GetEnumerator"/>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

/// <inheritdoc cref="IReadOnlyCollection{T}.Count"/>
public int Count => _errors.Count;
public static class Validator {
public static IValidator<T, TError> Value<T, TError>(T value) => new Validator<T, TError>(value);

public static IValidator<T, TError> Value<T, TError>(
T value,
Func<T, bool> predicate,
Func<TError> errorSelector
) => new Validator<T, TError>(value).Validate(predicate, errorSelector);

public static IValidator<T, TError> Value<T, TError>(
T value,
Func<T, bool> predicate,
TError errorSelector
) => new Validator<T, TError>(value).Validate(predicate, errorSelector);
}
}
10 changes: 5 additions & 5 deletions test/Lemonad.ErrorHandling.Test/Validator.Tests/ValidateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ public class ValidateTests {
[Fact]
public void Single_False_Validation() {
const string error = "Not dividable by 2";
var validator = new Validator<int, string>(1).Validate(i => i % 2 == 0, error).ToList();
var validator = ErrorHandling.Validator.Value<int, string>(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<int, string>(2).Validate(i => i % 2 == 0, error).ToList();
var validator = ErrorHandling.Validator.Value<int, string>(2).Validate(i => i % 2 == 0, error).ToList();
Assert.Empty(validator);
}

[Fact]
public void Double_True_Validation() {
const string error1 = "Not dividable by 2";
const string error2 = "Is equal to 2.";
var validator = new Validator<int, string>(2)
var validator = ErrorHandling.Validator.Value<int, string>(2)
.Validate(i => i % 2 == 0, error1)
.Validate(i => i == 2, () => error2)
.ToList();
Expand All @@ -32,7 +32,7 @@ public void Double_True_Validation() {
public void Double_False_Validation() {
const string error1 = "Is not equal to 0";
const string error2 = "Not dividable by 2";
var validator = new Validator<int, string>(1)
var validator = ErrorHandling.Validator.Value<int ,string>(1)
.Validate(i => i == 0, () => error1)
.Validate(i => i % 2 == 0, () => error2)
.ToList();
Expand All @@ -45,7 +45,7 @@ public void Double_False_Validation() {
public void Single_True_Validation_And_Single_False_Validation() {
const string error1 = "Is not equal to 0";
const string error2 = "Not dividable by 2";
var validator = new Validator<int, string>(2)
var validator = ErrorHandling.Validator.Value<int,string>(2)
.Validate(i => i == 0, () => error1)
.Validate(i => i % 2 == 0, () => error2)
.ToList();
Expand Down

0 comments on commit 3d41cb1

Please sign in to comment.