As alluded in the prior post, all of our return types so far have been ValidationResult - but our consumers will need to know whether they have a success or an error in order to make decisions.

We don’t want them to have to explicitly cast values - that would be boilerplate they’d need to write every time … and elimination of boilerplate is one of our primary motivations.

What we’ll do is to add a couple of public members to ValidationResult to take care of the conversions:

public abstract class ValidationResult
{
    protected static readonly IEnumerable<ErrorResult> _emptyErrors
        = new List<ErrorResult>();

    public abstract IEnumerable<ErrorResult> Errors();

    public abstract bool HasErrors { get; }
}

The property HasErrors allows our consumers to test if we have any errors at all, while Errors() allows them to iterate across those errors when they want all the gory details:

var validation = p.Validate();
if (validation.HasErrors)
{
    foreach (var e in validation.Errors())
    {
        Console.WriteLine(e.Message);
    }
}

The implementation for SuccessResult is straightforward - there’s no error to report:

public sealed class SuccessResult : ValidationResult
{
    public override IEnumerable<ErrorResult> Errors()
        => _emptyErrors;

    public override bool HasErrors => false;
}

The static member _emptyErrors is a little tweak that allows us to return an empty list of errors without having to create a new one each time.

Implementation for ErrorResult is a little more complex - but only just …

public sealed class ErrorResult : ValidationResult
{
    public override IEnumerable<ErrorResult> Errors()
    {
        yield return this;
    }

    public override bool HasErrors => true;
}

For AggregateResult, the results aren’t hard coded; instead, they have to be based on the messages contained in the _results hashset:

public sealed class AggregateResult : ValidationResult
{
    public override IEnumerable<ErrorResult> Errors()
    {
        return _results.OfType<ErrorResult>();
    }

    public override bool HasErrors => Errors().Any();
}

I’m somewhat torn by these implementations of Errors() - while they are clean and clear, they do result in the enumerable being recreated every time the collection is enumerated. One alternative would be to create internal lists, to return the same instance every time. However, I’m not convinced that the saving is worth the memory cost.

Next time we’ll return to the construction of AggregateResult and how we’ll handle that.

About this series

Seeking a better approach to validation.

Posts in this series

Why we need better validation
Basic validation
Recovery of validation types
Aggregation of validation
Short-circuiting validation
Equality of validation
Prior post in this series:
Basic validation
Next post in this series:
Aggregation of validation

Comments

blog comments powered by Disqus