ValueOf icon indicating copy to clipboard operation
ValueOf copied to clipboard

Validate without exceptions?

Open Bitfiddler opened this issue 2 years ago • 1 comments

I'm new to ValueObjects so forgive me if this is a bad idea, but I wanted to avoid throwing exceptions during validation for a variety of reasons and instead have a collection of validation errors. So I added:

private readonly List<TValidationResult> _errors;

and later:

public List<TValidationResult> Errors => _errors ?? new();

public IReadOnlyList<TValidationResult> ValidationErrors => _errors.AsReadOnly();

To the ValueOf base class. This allows the ValueOf consumer to decide whether Validate should throw exceptions, or TryValidate should just fail silently; or whether both should append one or more validation errors to a collection that can be displayed to a user or logged.

Is there a reason I'm unaware of as to why throwing exceptions seems to be the preferred validation mechanism?

Bitfiddler avatar May 10 '22 01:05 Bitfiddler

This allows the ValueOf consumer to decide whether Validate should throw exceptions, or TryValidate should just fail silently; or whether both should append one or more validation errors to a collection that can be displayed to a user or logged.

The consumer can still perform all of the validations on it's own, without having to call the Validate or TryValidate methods. Would it makes sense to "outsource" your validation logic to a separate class or a separate, re-usable method? In the code below, the CheckValidation method is public static - anyone can call it and see what errors, if any, would cause Validate to throw or TryValidate to fail. And depending on your needs, you can exit early from CheckValidation to stop accruing redundant errors.

public class EmailAddress : ValueOf<string, EmailAddress>
{
   protected override void Validate()
   {
      var errors = CheckValidation(Value);
      if (errors.Count > 0)
      {
         // Throw an exception that corresponds to your error
      }
   }

   protected override bool TryValidate()
   {
      return CheckValidation(Value).Count == 0;
   }

   public static List<StringPrimitiveValidationFailure> CheckValidation(string value)
   {
      var errors = new List<StringPrimitiveValidationFailure>();

      if (value is null)
      {
         errors.Add(StringPrimitiveValidationFailure.IsNull);
      }

      if (string.IsNullOrWhiteSpace(value))
      {
         errors.Add(StringPrimitiveValidationFailure.IsEmpty);
      }

      if (value.Trim().EndsWith('.'))
      {
         errors.Add(StringPrimitiveValidationFailure.Invalid);
      }

      try
      {
         var addr = new System.Net.Mail.MailAddress(value);
      }
      catch
      {
         errors.Add(StringPrimitiveValidationFailure.Invalid);
      }

      return errors;
   }
}

Jack-Edwards avatar May 18 '22 03:05 Jack-Edwards