mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Refactor recent PRs
This commit is contained in:
@@ -28,7 +28,7 @@ namespace CliFx.Demo
|
||||
public static async Task<int> Main() =>
|
||||
await new CliApplicationBuilder()
|
||||
.AddCommandsFromThisAssembly()
|
||||
.UseTypeActivator(GetServiceProvider().GetService)
|
||||
.UseTypeActivator(GetServiceProvider().GetRequiredService)
|
||||
.Build()
|
||||
.RunAsync();
|
||||
}
|
||||
|
||||
@@ -1,18 +1,55 @@
|
||||
namespace CliFx
|
||||
{
|
||||
/// <summary>
|
||||
/// A base type for custom validators.
|
||||
/// Represents a result of a validation.
|
||||
/// </summary>
|
||||
public partial class ValidationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether validation was successful.
|
||||
/// </summary>
|
||||
public bool IsValid => ErrorMessage == null;
|
||||
|
||||
/// <summary>
|
||||
/// If validation has failed, contains the associated error, otherwise null.
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="ValidationResult"/>.
|
||||
/// </summary>
|
||||
public ValidationResult(string? errorMessage = null) =>
|
||||
ErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public partial class ValidationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates successful result, meaning that the validation has passed.
|
||||
/// </summary>
|
||||
public static ValidationResult Ok() => new ValidationResult();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an error result, meaning that the validation has failed.
|
||||
/// </summary>
|
||||
public static ValidationResult Error(string message) => new ValidationResult(message);
|
||||
}
|
||||
|
||||
internal interface IArgumentValueValidator
|
||||
{
|
||||
ValidationResult Validate(object value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A base type for custom argument validators.
|
||||
/// </summary>
|
||||
public abstract class ArgumentValueValidator<T> : IArgumentValueValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Your validation logic have to be implemented in this method.
|
||||
/// Validates the value.
|
||||
/// </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);
|
||||
ValidationResult IArgumentValueValidator.Validate(object value) => Validate((T) value);
|
||||
}
|
||||
}
|
||||
|
||||
28
CliFx/Attributes/CommandArgumentAttribute.cs
Normal file
28
CliFx/Attributes/CommandArgumentAttribute.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace CliFx.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Properties shared by parameter and option arguments.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public abstract class CommandArgumentAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Option description, which is used in help text.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of converter to use when mapping the argument value.
|
||||
/// Converter must implement <see cref="IArgumentValueConverter"/>.
|
||||
/// </summary>
|
||||
public Type? Converter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Types of validators to use when mapping the argument value.
|
||||
/// Validators must derive from <see cref="ArgumentValueValidator{T}"/>.
|
||||
/// </summary>
|
||||
public Type[] Validators { get; set; } = Array.Empty<Type>();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace CliFx.Attributes
|
||||
/// Annotates a property that defines a command option.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class CommandOptionAttribute : Attribute
|
||||
public class CommandOptionAttribute : CommandArgumentAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Option name (must be longer than a single character).
|
||||
@@ -27,27 +27,11 @@ namespace CliFx.Attributes
|
||||
/// </summary>
|
||||
public bool IsRequired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Option description, which is used in help text.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Environment variable that will be used as fallback if no option value is specified.
|
||||
/// </summary>
|
||||
public string? EnvironmentVariableName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of converter to use when mapping the argument value.
|
||||
/// Converter must implement <see cref="IArgumentValueConverter"/>.
|
||||
/// </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>
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace CliFx.Attributes
|
||||
/// Annotates a property that defines a command parameter.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class CommandParameterAttribute : Attribute
|
||||
public class CommandParameterAttribute : CommandArgumentAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Order of this parameter compared to other parameters.
|
||||
@@ -21,22 +21,6 @@ namespace CliFx.Attributes
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parameter description, which is used in help text.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of converter to use when mapping the argument value.
|
||||
/// Converter must implement <see cref="IArgumentValueConverter"/>.
|
||||
/// </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>
|
||||
|
||||
@@ -19,14 +19,18 @@ namespace CliFx.Domain
|
||||
|
||||
public Type? ConverterType { get; }
|
||||
|
||||
public readonly Type[]? ValidatorTypes;
|
||||
public Type[] ValidatorTypes { get; }
|
||||
|
||||
protected CommandArgumentSchema(PropertyInfo? property, string? description, Type? converterType = null, Type[]? validators = null)
|
||||
protected CommandArgumentSchema(
|
||||
PropertyInfo? property,
|
||||
string? description,
|
||||
Type? converterType,
|
||||
Type[] validatorTypes)
|
||||
{
|
||||
Property = property;
|
||||
Description = description;
|
||||
ConverterType = converterType;
|
||||
ValidatorTypes = validators;
|
||||
ValidatorTypes = validatorTypes;
|
||||
}
|
||||
|
||||
private Type? TryGetEnumerableArgumentUnderlyingType() =>
|
||||
@@ -135,12 +139,28 @@ namespace CliFx.Domain
|
||||
}
|
||||
}
|
||||
|
||||
private void Validate(object? value)
|
||||
{
|
||||
if (value is null)
|
||||
return;
|
||||
|
||||
var validators = ValidatorTypes
|
||||
.Select(t => t.CreateInstance<IArgumentValueValidator>())
|
||||
.ToArray();
|
||||
|
||||
var failedValidations = validators
|
||||
.Select(v => v.Validate(value))
|
||||
.Where(result => !result.IsValid)
|
||||
.ToArray();
|
||||
|
||||
if (failedValidations.Any())
|
||||
throw CliFxException.ValidationFailed(this, failedValidations);
|
||||
}
|
||||
|
||||
public void BindOn(ICommand command, IReadOnlyList<string> values)
|
||||
{
|
||||
var value = Convert(values);
|
||||
|
||||
if (ValidatorTypes.NotEmpty())
|
||||
Validate(value);
|
||||
Validate(value);
|
||||
|
||||
Property?.SetValue(command, value);
|
||||
}
|
||||
@@ -163,25 +183,6 @@ 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 ValidatorTypes!)
|
||||
{
|
||||
var result = validator.CreateInstance<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
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace CliFx.Domain
|
||||
string? environmentVariableName,
|
||||
bool isRequired,
|
||||
string? description,
|
||||
Type? converterType = null,
|
||||
Type[]? validatorTypes = null)
|
||||
Type? converterType,
|
||||
Type[] validatorTypes)
|
||||
: base(property, description, converterType, validatorTypes)
|
||||
{
|
||||
Name = name;
|
||||
@@ -108,10 +108,26 @@ namespace CliFx.Domain
|
||||
|
||||
internal partial class CommandOptionSchema
|
||||
{
|
||||
public static CommandOptionSchema HelpOption { get; } =
|
||||
new CommandOptionSchema(null, "help", 'h', null, false, "Shows help text.", converterType: null, validatorTypes: null);
|
||||
public static CommandOptionSchema HelpOption { get; } = new CommandOptionSchema(
|
||||
null,
|
||||
"help",
|
||||
'h',
|
||||
null,
|
||||
false,
|
||||
"Shows help text.",
|
||||
null,
|
||||
Array.Empty<Type>()
|
||||
);
|
||||
|
||||
public static CommandOptionSchema VersionOption { get; } =
|
||||
new CommandOptionSchema(null, "version", null, null, false, "Shows version information.", converterType: null, validatorTypes: null);
|
||||
public static CommandOptionSchema VersionOption { get; } = new CommandOptionSchema(
|
||||
null,
|
||||
"version",
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
"Shows version information.",
|
||||
null,
|
||||
Array.Empty<Type>()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ namespace CliFx.Domain
|
||||
int order,
|
||||
string name,
|
||||
string? description,
|
||||
Type? converterType = null,
|
||||
Type[]? validatorTypes = null)
|
||||
Type? converterType,
|
||||
Type[] validatorTypes)
|
||||
: base(property, description, converterType, validatorTypes)
|
||||
{
|
||||
Order = order;
|
||||
|
||||
@@ -283,7 +283,7 @@ namespace CliFx.Domain
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCommandChildren(
|
||||
private void WriteCommandDescendants(
|
||||
CommandSchema command,
|
||||
IReadOnlyList<CommandSchema> descendantCommands)
|
||||
{
|
||||
@@ -297,12 +297,7 @@ namespace CliFx.Domain
|
||||
|
||||
foreach (var descendantCommand in descendantCommands)
|
||||
{
|
||||
var relativeCommandName = !string.IsNullOrWhiteSpace(command.Name)
|
||||
? descendantCommand.Name!.Substring(command.Name.Length).Trim()
|
||||
: descendantCommand.Name!;
|
||||
|
||||
// Description
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(descendantCommand.Description))
|
||||
{
|
||||
WriteHorizontalMargin();
|
||||
@@ -310,7 +305,7 @@ namespace CliFx.Domain
|
||||
WriteVerticalMargin();
|
||||
}
|
||||
|
||||
// Name
|
||||
// Usage
|
||||
WriteHorizontalMargin(4);
|
||||
WriteCommandUsageLineItem(descendantCommand, false);
|
||||
|
||||
@@ -355,7 +350,7 @@ namespace CliFx.Domain
|
||||
WriteCommandUsage(command, descendantCommands);
|
||||
WriteCommandParameters(command);
|
||||
WriteCommandOptions(command, defaultValues);
|
||||
WriteCommandChildren(command, descendantCommands);
|
||||
WriteCommandDescendants(command, descendantCommands);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace CliFx.Domain
|
||||
}
|
||||
|
||||
var invalidValidatorParameters = command.Parameters
|
||||
.Where(p => p.ValidatorTypes != null && !p.ValidatorTypes.All(x => x.Implements(typeof(IArgumentValueValidator))))
|
||||
.Where(p => !p.ValidatorTypes.All(x => x.Implements(typeof(IArgumentValueValidator))))
|
||||
.ToArray();
|
||||
|
||||
if (invalidValidatorParameters.Any())
|
||||
@@ -222,7 +222,7 @@ namespace CliFx.Domain
|
||||
}
|
||||
|
||||
var invalidValidatorOptions = command.Options
|
||||
.Where(o => o.ValidatorTypes != null && !o.ValidatorTypes.All(x => x.Implements(typeof(IArgumentValueValidator))))
|
||||
.Where(o => !o.ValidatorTypes.All(x => x.Implements(typeof(IArgumentValueValidator))))
|
||||
.ToArray();
|
||||
|
||||
if (invalidValidatorOptions.Any())
|
||||
|
||||
@@ -36,7 +36,8 @@ namespace CliFx.Exceptions
|
||||
{
|
||||
internal static CliFxException DefaultActivatorFailed(Type type, Exception? innerException = null)
|
||||
{
|
||||
var configureActivatorMethodName = $"{nameof(CliApplicationBuilder)}.{nameof(CliApplicationBuilder.UseTypeActivator)}(...)";
|
||||
var configureActivatorMethodName =
|
||||
$"{nameof(CliApplicationBuilder)}.{nameof(CliApplicationBuilder.UseTypeActivator)}(...)";
|
||||
|
||||
var message = $@"
|
||||
Failed to create an instance of type '{type.FullName}'.
|
||||
@@ -193,7 +194,7 @@ Specified converter must implement {typeof(IArgumentValueConverter).FullName}.";
|
||||
Command '{command.Type.FullName}' is invalid because it contains {invalidParameters.Count} parameter(s) with invalid value validators:
|
||||
{invalidParameters.JoinToString(Environment.NewLine)}
|
||||
|
||||
Specified validator(s) must inherit from {typeof(ArgumentValueValidator<>).FullName}.";
|
||||
Specified validators must inherit from {typeof(ArgumentValueValidator<>).FullName}.";
|
||||
|
||||
return new CliFxException(message.Trim());
|
||||
}
|
||||
@@ -424,7 +425,8 @@ Missing values for one or more required options:
|
||||
return new CliFxException(message.Trim());
|
||||
}
|
||||
|
||||
internal static CliFxException UnrecognizedParametersProvided(IReadOnlyList<CommandParameterInput> parameterInputs)
|
||||
internal static CliFxException UnrecognizedParametersProvided(
|
||||
IReadOnlyList<CommandParameterInput> parameterInputs)
|
||||
{
|
||||
var message = $@"
|
||||
Unrecognized parameters provided:
|
||||
@@ -442,12 +444,35 @@ Unrecognized options provided:
|
||||
return new CliFxException(message.Trim());
|
||||
}
|
||||
|
||||
internal static CliFxException ValueValidationFailed(CommandArgumentSchema argument, IEnumerable<string> errors)
|
||||
internal static CliFxException ValidationFailed(
|
||||
CommandParameterSchema parameter,
|
||||
IReadOnlyList<ValidationResult> failedResults)
|
||||
{
|
||||
var message = $@"
|
||||
The validation of the provided value for {argument.Property!.Name} is failed because: {errors.JoinToString(Environment.NewLine)}";
|
||||
Value provided for parameter {parameter.GetUserFacingDisplayString()}:
|
||||
{failedResults.Select(r => r.ErrorMessage).JoinToString(Environment.NewLine)}";
|
||||
|
||||
return new CliFxException(message.Trim());
|
||||
}
|
||||
|
||||
internal static CliFxException ValidationFailed(
|
||||
CommandOptionSchema option,
|
||||
IReadOnlyList<ValidationResult> failedResults)
|
||||
{
|
||||
var message = $@"
|
||||
Value provided for option {option.GetUserFacingDisplayString()}:
|
||||
{failedResults.Select(r => r.ErrorMessage).JoinToString(Environment.NewLine)}";
|
||||
|
||||
return new CliFxException(message.Trim());
|
||||
}
|
||||
|
||||
internal static CliFxException ValidationFailed(
|
||||
CommandArgumentSchema argument,
|
||||
IReadOnlyList<ValidationResult> failedResults) => argument switch
|
||||
{
|
||||
CommandParameterSchema parameter => ValidationFailed(parameter, failedResults),
|
||||
CommandOptionSchema option => ValidationFailed(option, failedResults),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(argument))
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace CliFx
|
||||
{
|
||||
internal interface IArgumentValueValidator
|
||||
{
|
||||
ValidationResult Validate(object value);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace CliFx.Internal.Extensions
|
||||
{
|
||||
@@ -10,11 +9,5 @@ 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();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
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 };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user