Skip to content

System.CommandLine: Validation #84178

Open
@adamsitnik

Description

@adamsitnik

Background and motivation

In #68578 (comment), we have introduced a set of symbol types for building a parsable hierarchy.
In #84177 (comment) a set of parsing APIs that expose information required by validators.

In this issue, we want to propose a set of APIs for validation.

API Proposal

namespace System.CommandLine;

public abstract class CliArgument : CliSymbol
{
    /// <summary>
    /// Provides a list of argument validators. Validators can be used
    /// to provide custom errors based on user input.
    /// </summary>
    public List<Action<ArgumentResult>> Validators { get; }
    
    /// <summary>
    /// Configures the argument to accept only the specified values, and to suggest them as command line completions.
    /// </summary>
    /// <param name="values">The values that are allowed for the argument.</param>
    public void AcceptOnlyFromAmong(params string[] values);

    /// <summary>
    /// Configures the argument to accept only values representing legal file paths.
    /// </summary>
    public void AcceptLegalFilePathsOnly();

    /// <summary>
    /// Configures the argument to accept only values representing legal file names.
    /// </summary>
    /// <remarks>A parse error will result, for example, if file path separators are found in the parsed value.</remarks>
    public void AcceptLegalFileNamesOnly();
}

/// <summary>
/// Provides extension methods for <see cref="CliArgument" />.
/// </summary>
public static class ArgumentValidation
{
    /// <summary>
    /// Configures an argument to accept only values corresponding to an existing file.
    /// </summary>
    /// <param name="argument">The argument to configure.</param>
    /// <returns>The configured argument.</returns>
    public static CliArgument<FileInfo> AcceptExistingOnly(this CliArgument<FileInfo> argument);

    public static CliArgument<DirectoryInfo> AcceptExistingOnly(this CliArgument<DirectoryInfo> argument);

    public static CliArgument<FileSystemInfo> AcceptExistingOnly(this CliArgument<FileSystemInfo> argument);

    public static CliArgument<T> AcceptExistingOnly<T>(this CliArgument<T> argument)
        where T : IEnumerable<FileSystemInfo>
}


public abstract class CliOption : CliSymbol
{
    /// <summary>
    /// Validators that will be called when the option is matched by the parser.
    /// </summary>
    public List<Action<OptionResult>> Validators { get; }
    
    /// <summary>
    /// Configures the option to accept only the specified values, and to suggest them as command line completions.
    /// </summary>
    /// <param name="values">The values that are allowed for the option.</param>
    public void AcceptOnlyFromAmong(params string[] values);

    /// <summary>
    /// Configures the option to accept only values representing legal file paths.
    /// </summary>
    public void AcceptLegalFilePathsOnly();

    /// <summary>
    /// Configures the option to accept only values representing legal file names.
    /// </summary>
    /// <remarks>A parse error will result, for example, if file path separators are found in the parsed value.</remarks>
    public void AcceptLegalFileNamesOnly();
}

/// <summary>
/// Provides extension methods for <see cref="CliOption" />.
/// </summary>
public static class OptionValidation
{
    public static CliOption<FileInfo> AcceptExistingOnly(this CliOption<FileInfo> option);
    
    public static CliOption<DirectoryInfo> AcceptExistingOnly(this CliOption<DirectoryInfo> option);

    public static CliOption<FileSystemInfo> AcceptExistingOnly(this CliOption<FileSystemInfo> option);

    public static CliOption<T> AcceptExistingOnly<T>(this CliOption<T> option)
        where T : IEnumerable<FileSystemInfo>
}

public class CliCommand : CliSymbol, IEnumerable<CliSymbol>
{
    /// <summary>
    /// Validators to the command. Validators can be used
    /// to create custom validation logic.
    /// </summary>
    public List<Action<CommandResult>> Validators { get; }
}

API Usage

Defining a custom validator for option:

CliOption<int> option = new ("-x");
option.Validators.Add(result =>
{
    int value = result.GetValue(option);

    if (value < 0 || value > 100)
    {
        result.AddError("The value of option '-x' must be between 1 and 100.");
    }
});

Using built-in validator to ensure that the input file exists:

CliCommand command = new ("move")
{
    new CliArgument<FileInfo>("from").AcceptExistingOnly(),
    new CliArgument<FileInfo>("to")
};

Risks

AcceptLegalFilePathsOnly and AcceptLegalFileNamesOnly are not constrained to any specific T, while they make sense only for string, FileInfo, DirectoryInfo, FileSystemInfo and types implementing IEnumerable<T> for those types.

Metadata

Metadata

Labels

api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Console

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions