Collecting Errors


Validation is a tough subject. One that I’m constantly trying to think of better ways of doing. Some suggest that all validation should occur in the domain, and some prefer to check if the object is valid before proceeding. I lean towards the idea of not allowing your objects to enter an invalid state. So far the easiest approach I have found to do this is to raise meaningful exceptions in the domain to ensure this.

However, when there are several reasons why an object can be considered “invalid” and, those reasons need to be reflected in the UI, I haven’t been able to figure out a clean way to do this in the domain. Suggestions are welcome.

Here’s an approach that we’ve taken to some of our validation, when user input needs to be checked so that we can provide meaningful error messages to the end user.

First we have 2 core validation interfaces:

public interface IValidationResult
{
    bool IsValid { get; }
    IEnumerable<string> BrokenRules { get; }
}

and

public interface IValidation<T>
{
    IValidationResult Validate(T item);
}

The IValidation is in essence a form of a Specification. Now to collect the errors we use a visitor. The following are the core visitor interfaces.

public interface IVisitor<T>
{
    void Visit(T item_to_visit);
}

public interface IValueReturningVisitor<TypeToVisit, TypeToReturn> : IVisitor<TypeToVisit>
{
    void Reset();
    TypeToReturn Result { get; }
}

We have an implementation of a IValueReturningVisitor that collects errors from visiting IValidations, then returns a validation result.

public class ErrorCollectingVisitor<T> : IValueReturningVisitor<IValidation<T>, IValidationResult>
{
    private readonly T item_to_validate;
    private readonly List<string> results;

    public ErrorCollectingVisitor(T item_to_validate)
    {
        this.item_to_validate = item_to_validate;
        results = new List<string>();
    }

    public void Visit(IValidation<T> item_to_visit)
    {
        var validation_result = item_to_visit.Validate(item_to_validate);
        if (!validation_result.IsValid)
        {
            results.AddRange(validation_result.BrokenRules);
        }
    }

    public void Reset()
    {
        results.Clear();
    }

    public IValidationResult Result
    {
        get { return new ValidationResult(results.Count == 0, results); }
    }
}

And a handy extension method for returning the value from visiting a set of validations.

public static Result ReturnValueFromVisitingAllItemsWith<TypeToVisit, Result>(
    this IEnumerable<TypeToVisit> items_to_visit, IValueReturningVisitor<TypeToVisit, Result> visitor)
{
    visitor.Reset();
    items_to_visit.Each(x => visitor.Visit(x));
    return visitor.Result;
}

An example of the usage for the visit can be seen below:

public IValidationResult Validate(IUser user)
{
    return userValidations
        .Select(x => x as IValidation<IUser>)
        .ReturnValueFromVisitingAllItemsWith(new ErrorCollectingVisitor<IUser>(user));
}

JetBrains Seeder Program