add a base type for custom validators

This commit is contained in:
Oleksandr Shustov
2020-11-06 20:37:46 +02:00
parent 6a38c04c11
commit b8c60717d5
10 changed files with 130 additions and 9 deletions

View File

@@ -0,0 +1,18 @@
namespace CliFx
{
/// <summary>
/// A base type for custom validators.
/// </summary>
public abstract class ArgumentValueValidator<T> : IArgumentValueValidator
{
/// <summary>
/// Your validation logic have to be implemented in this method.
/// </summary>
public abstract ValidationResult Validate(T value);
/// <summary>
/// Non-generic method, will be called by the framework.
/// </summary>
public ValidationResult Validate(object value) => Validate((T) value);
}
}

View File

@@ -42,6 +42,11 @@ namespace CliFx.Attributes
/// </summary>
public Type? Converter { get; set; }
/// <summary>
/// Type of a converter to use for the option value evaluating.
/// </summary>
public Type[]? Validators { get; set; }
/// <summary>
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
/// </summary>

View File

@@ -31,6 +31,11 @@ namespace CliFx.Attributes
/// </summary>
public Type? Converter { get; set; }
/// <summary>
/// Type of a converter to use for the option value evaluating.
/// </summary>
public Type[]? Validators { get; set; }
/// <summary>
/// Initializes an instance of <see cref="CommandParameterAttribute"/>.
/// </summary>

View File

@@ -19,11 +19,15 @@ namespace CliFx.Domain
protected Type? Converter { get; set; }
protected CommandArgumentSchema(PropertyInfo? property, string? description, Type? converter = null)
private readonly Type[]? _validators;
protected CommandArgumentSchema(PropertyInfo? property, string? description, Type? converter = null, Type[]? validators = null)
{
Property = property;
Description = description;
Converter = converter;
_validators = validators;
}
private Type? TryGetEnumerableArgumentUnderlyingType() =>
@@ -120,8 +124,15 @@ namespace CliFx.Domain
}
}
public void BindOn(ICommand command, IReadOnlyList<string> values) =>
Property?.SetValue(command, Convert(values));
public void BindOn(ICommand command, IReadOnlyList<string> values)
{
var value = Convert(values);
if (_validators.NotEmpty())
Validate(value);
Property?.SetValue(command, value);
}
public void BindOn(ICommand command, params string[] values) =>
BindOn(command, (IReadOnlyList<string>) values);
@@ -141,6 +152,25 @@ namespace CliFx.Domain
return Array.Empty<string>();
}
private void Validate(object? value)
{
if (value is null)
return;
var failed = new List<ValidationResult>();
foreach (var validator in _validators!)
{
var result = validator.InstanceOf<IArgumentValueValidator>().Validate(value!);
if (result.IsValid)
continue;
failed.Add(result);
}
if (failed.NotEmpty())
throw CliFxException.ValueValidationFailed(this, failed.Select(x => x.ErrorMessage!));
}
}
internal partial class CommandArgumentSchema

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
@@ -24,8 +25,9 @@ namespace CliFx.Domain
string? environmentVariableName,
bool isRequired,
string? description,
Type? converter = null)
: base(property, description, converter)
Type? converter = null,
Type[]? validators = null)
: base(property, description, converter, validators)
{
Name = name;
ShortName = shortName;
@@ -99,7 +101,8 @@ namespace CliFx.Domain
attribute.EnvironmentVariableName,
attribute.IsRequired,
attribute.Description,
attribute.Converter
attribute.Converter,
attribute.Validators
);
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
@@ -12,8 +13,14 @@ namespace CliFx.Domain
public string Name { get; }
public CommandParameterSchema(PropertyInfo? property, int order, string name, string? description, Type? converter = null)
: base(property, description, converter)
public CommandParameterSchema(
PropertyInfo? property,
int order,
string name,
string? description,
Type? converter = null,
Type[]? validators = null)
: base(property, description, converter, validators)
{
Order = order;
Name = name;
@@ -52,7 +59,8 @@ namespace CliFx.Domain
attribute.Order,
name,
attribute.Description,
attribute.Converter
attribute.Converter,
attribute.Validators
);
}
}

View File

@@ -389,5 +389,13 @@ Unrecognized options provided:
return new CliFxException(message.Trim());
}
internal static CliFxException ValueValidationFailed(CommandArgumentSchema argument, IEnumerable<string> errors)
{
var message = $@"
The validation of the provided value for {argument.Property!.Name} is failed because: {errors.JoinToString(Environment.NewLine)}";
return new CliFxException(message.Trim());
}
}
}

View File

@@ -0,0 +1,7 @@
namespace CliFx
{
internal interface IArgumentValueValidator
{
ValidationResult Validate(object value);
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
namespace CliFx.Internal.Extensions
{
@@ -9,5 +10,11 @@ namespace CliFx.Internal.Extensions
foreach (var item in items)
source.Remove(item);
}
public static bool IsNullOrEmpty<T>(this IEnumerable<T>? source) =>
!source?.Any() ?? true;
public static bool NotEmpty<T>(this IEnumerable<T>? source) =>
!source.IsNullOrEmpty();
}
}

30
CliFx/ValidationResult.cs Normal file
View File

@@ -0,0 +1,30 @@
namespace CliFx
{
/// <summary>
/// A tiny object that represents a result of the validation.
/// </summary>
public class ValidationResult
{
/// <summary>
/// False if there is no error message, otherwise - true.
/// </summary>
public bool IsValid => ErrorMessage == null;
/// <summary>
/// Contains an information about the reasons of failed validation.
/// </summary>
public string? ErrorMessage { get; private set; }
private ValidationResult() { }
/// <summary>
/// Creates Ok result, means that the validation is passed.
/// </summary>
public static ValidationResult Ok() => new ValidationResult() { };
/// <summary>
/// Creates Error result, means that the validation failed.
/// </summary>
public static ValidationResult Error(string message) => new ValidationResult() { ErrorMessage = message };
}
}