using System;
using System.Linq;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Unosquare.Swan.Abstractions;
namespace Unosquare.Swan.Components {
///
/// Represents an object validator.
///
///
/// The following code describes how to perform a simple object validation.
///
/// using Unosquare.Swan.Components;
///
/// class Example
/// {
/// public static void Main()
/// {
/// // create an instance of ObjectValidator
/// var obj = new ObjectValidator();
///
/// // Add a validation to the 'Simple' class with a custom error message
/// obj.AddValidator<Simple>(x =>
/// !string.IsNullOrEmpty(x.Name), "Name must not be empty");
///
/// // check if object is valid
/// var res = obj.IsValid(new Simple { Name = "Name" });
/// }
///
/// class Simple
/// {
/// public string Name { get; set; }
/// }
/// }
///
///
/// The following code shows of to validate an object with a custom validator and some attributes using the Runtime ObjectValidator singleton.
///
/// using Unosquare.Swan.Components;
///
/// class Example
/// {
/// public static void Main()
/// {
/// // create an instance of ObjectValidator
/// Runtime.ObjectValidator
/// .AddValidator<Simple>(x =>
/// !x.Name.Equals("Name"), "Name must not be 'Name'");
///
/// // validate object
/// var res = Runtime.ObjectValidator
/// .Validate(new Simple{ Name = "name", Number = 5, Email ="email@mail.com"})
/// }
///
/// class Simple
/// {
/// [NotNull]
/// public string Name { get; set; }
///
/// [Range(1, 10)]
/// public int Number { get; set; }
///
/// [Email]
/// public string Email { get; set; }
/// }
/// }
///
///
public class ObjectValidator {
private readonly ConcurrentDictionary>> _predicates =
new ConcurrentDictionary>>();
///
/// Validates an object given the specified validators and attributes.
///
/// The type of the object.
/// The object.
/// A validation result.
public ObjectValidationResult Validate(T obj) {
ObjectValidationResult errorList = new ObjectValidationResult();
_ = this.ValidateObject(obj, false, errorList.Add);
return errorList;
}
///
/// Validates an object given the specified validators and attributes.
///
/// The type.
/// The object.
///
/// true if the specified object is valid; otherwise, false.
///
/// obj.
public Boolean IsValid(T obj) => this.ValidateObject(obj);
///
/// Adds a validator to a specific class.
///
/// The type of the object.
/// The predicate that will be evaluated.
/// The message.
///
/// predicate
/// or
/// message.
///
public void AddValidator(Predicate predicate, String message)
where T : class {
if(predicate == null) {
throw new ArgumentNullException(nameof(predicate));
}
if(String.IsNullOrEmpty(message)) {
throw new ArgumentNullException(message);
}
if(!this._predicates.TryGetValue(typeof(T), out List> existing)) {
existing = new List>();
this._predicates[typeof(T)] = existing;
}
existing.Add(Tuple.Create((Delegate)predicate, message));
}
private Boolean ValidateObject(T obj, Boolean returnOnError = true, Action action = null) {
if(Equals(obj, null)) {
throw new ArgumentNullException(nameof(obj));
}
if(this._predicates.ContainsKey(typeof(T))) {
foreach(Tuple validation in this._predicates[typeof(T)]) {
if((Boolean)validation.Item1.DynamicInvoke(obj)) {
continue;
}
action?.Invoke(String.Empty, validation.Item2);
if(returnOnError) {
return false;
}
}
}
Dictionary> properties = Runtime.AttributeCache.RetrieveFromType(typeof(IValidator));
foreach(KeyValuePair> prop in properties) {
foreach(Object attribute in prop.Value) {
IValidator val = (IValidator)attribute;
if(val.IsValid(prop.Key.GetValue(obj, null))) {
continue;
}
action?.Invoke(prop.Key.Name, val.ErrorMessage);
if(returnOnError) {
return false;
}
}
}
return true;
}
}
///
/// Defines a validation result containing all validation errors and their properties.
///
public class ObjectValidationResult {
///
/// A list of errors.
///
public List Errors { get; set; } = new List();
///
/// true if there are no errors; otherwise, false.
///
public Boolean IsValid => !this.Errors.Any();
///
/// Adds an error with a specified property name.
///
/// The property name.
/// The error message.
public void Add(String propertyName, String errorMessage) =>
this.Errors.Add(new ValidationError { ErrorMessage = errorMessage, PropertyName = propertyName });
///
/// Defines a validation error.
///
public class ValidationError {
///
/// The property name.
///
public String PropertyName {
get; set;
}
///
/// The message error.
///
public String ErrorMessage {
get; set;
}
}
}
}