Add guards

This commit is contained in:
Alexey Golub
2019-08-14 13:49:14 +03:00
parent 7dfbb40860
commit 5a08b8c19b
24 changed files with 160 additions and 59 deletions

View File

@@ -23,7 +23,7 @@ namespace CliFx.Attributes
/// </summary>
public CommandAttribute(string name)
{
Name = name;
Name = name; // can be null
}
/// <summary>

View File

@@ -38,8 +38,8 @@ namespace CliFx.Attributes
/// </summary>
public CommandOptionAttribute(string name, char? shortName)
{
Name = name;
ShortName = shortName;
Name = name; // can be null
ShortName = shortName; // can be null
}
/// <summary>

View File

@@ -32,15 +32,15 @@ namespace CliFx
IConsole console, ICommandInputParser commandInputParser, ICommandSchemaResolver commandSchemaResolver,
ICommandFactory commandFactory, ICommandInitializer commandInitializer, ICommandHelpTextRenderer commandHelpTextRenderer)
{
_applicationMetadata = applicationMetadata;
_commandTypes = commandTypes;
_applicationMetadata = applicationMetadata.GuardNotNull(nameof(applicationMetadata));
_commandTypes = commandTypes.GuardNotNull(nameof(commandTypes));
_console = console;
_commandInputParser = commandInputParser;
_commandSchemaResolver = commandSchemaResolver;
_commandFactory = commandFactory;
_commandInitializer = commandInitializer;
_commandHelpTextRenderer = commandHelpTextRenderer;
_console = console.GuardNotNull(nameof(console));
_commandInputParser = commandInputParser.GuardNotNull(nameof(commandInputParser));
_commandSchemaResolver = commandSchemaResolver.GuardNotNull(nameof(commandSchemaResolver));
_commandFactory = commandFactory.GuardNotNull(nameof(commandFactory));
_commandInitializer = commandInitializer.GuardNotNull(nameof(commandInitializer));
_commandHelpTextRenderer = commandHelpTextRenderer.GuardNotNull(nameof(commandHelpTextRenderer));
}
private IReadOnlyList<string> GetAvailableCommandSchemasValidationErrors(IReadOnlyList<CommandSchema> availableCommandSchemas)
@@ -107,6 +107,8 @@ namespace CliFx
/// <inheritdoc />
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments)
{
commandLineArguments.GuardNotNull(nameof(commandLineArguments));
try
{
var commandInput = _commandInputParser.ParseInput(commandLineArguments);

View File

@@ -25,13 +25,18 @@ namespace CliFx
/// <inheritdoc />
public ICliApplicationBuilder WithCommand(Type commandType)
{
commandType.GuardNotNull(nameof(commandType));
_commandTypes.Add(commandType);
return this;
}
/// <inheritdoc />
public ICliApplicationBuilder WithCommandsFrom(Assembly commandAssembly)
{
commandAssembly.GuardNotNull(nameof(commandAssembly));
var commandTypes = commandAssembly.ExportedTypes.Where(t => t.Implements(typeof(ICommand)));
foreach (var commandType in commandTypes)
@@ -43,35 +48,35 @@ namespace CliFx
/// <inheritdoc />
public ICliApplicationBuilder UseTitle(string title)
{
_title = title;
_title = title.GuardNotNull(nameof(title));
return this;
}
/// <inheritdoc />
public ICliApplicationBuilder UseExecutableName(string executableName)
{
_executableName = executableName;
_executableName = executableName.GuardNotNull(nameof(executableName));
return this;
}
/// <inheritdoc />
public ICliApplicationBuilder UseVersionText(string version)
public ICliApplicationBuilder UseVersionText(string versionText)
{
_versionText = version;
_versionText = versionText.GuardNotNull(nameof(versionText));
return this;
}
/// <inheritdoc />
public ICliApplicationBuilder UseConsole(IConsole console)
{
_console = console;
_console = console.GuardNotNull(nameof(console));
return this;
}
/// <inheritdoc />
public ICliApplicationBuilder UseCommandFactory(ICommandFactory factory)
{
_commandFactory = factory;
_commandFactory = factory.GuardNotNull(nameof(factory));
return this;
}

View File

@@ -1,4 +1,5 @@
using System;
using CliFx.Internal;
namespace CliFx.Exceptions
{
@@ -19,7 +20,7 @@ namespace CliFx.Exceptions
public CommandErrorException(int exitCode, string message, Exception innerException)
: base(message, innerException)
{
ExitCode = exitCode;
ExitCode = exitCode.GuardNotZero(nameof(exitCode));
}
/// <summary>

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using CliFx.Internal;
using CliFx.Services;
namespace CliFx
@@ -15,6 +16,9 @@ namespace CliFx
/// </summary>
public static ICliApplicationBuilder WithCommands(this ICliApplicationBuilder builder, IReadOnlyList<Type> commandTypes)
{
builder.GuardNotNull(nameof(builder));
commandTypes.GuardNotNull(nameof(commandTypes));
foreach (var commandType in commandTypes)
builder.WithCommand(commandType);
@@ -26,6 +30,9 @@ namespace CliFx
/// </summary>
public static ICliApplicationBuilder WithCommandsFrom(this ICliApplicationBuilder builder, IReadOnlyList<Assembly> commandAssemblies)
{
builder.GuardNotNull(nameof(builder));
commandAssemblies.GuardNotNull(nameof(commandAssemblies));
foreach (var commandAssembly in commandAssemblies)
builder.WithCommandsFrom(commandAssembly);
@@ -35,13 +42,21 @@ namespace CliFx
/// <summary>
/// Adds commands from calling assembly to the application.
/// </summary>
public static ICliApplicationBuilder WithCommandsFromThisAssembly(this ICliApplicationBuilder builder) =>
builder.WithCommandsFrom(Assembly.GetCallingAssembly());
public static ICliApplicationBuilder WithCommandsFromThisAssembly(this ICliApplicationBuilder builder)
{
builder.GuardNotNull(nameof(builder));
return builder.WithCommandsFrom(Assembly.GetCallingAssembly());
}
/// <summary>
/// Configures application to use specified factory method for creating new instances of <see cref="ICommand"/>.
/// </summary>
public static ICliApplicationBuilder UseCommandFactory(this ICliApplicationBuilder builder, Func<Type, ICommand> factoryMethod) =>
builder.UseCommandFactory(new DelegateCommandFactory(factoryMethod));
public static ICliApplicationBuilder UseCommandFactory(this ICliApplicationBuilder builder, Func<Type, ICommand> factoryMethod)
{
builder.GuardNotNull(nameof(builder));
factoryMethod.GuardNotNull(nameof(factoryMethod));
return builder.UseCommandFactory(new DelegateCommandFactory(factoryMethod));
}
}
}

View File

@@ -32,7 +32,7 @@ namespace CliFx
/// <summary>
/// Sets application version text, which appears in the help text and when the user requests version information.
/// </summary>
ICliApplicationBuilder UseVersionText(string version);
ICliApplicationBuilder UseVersionText(string versionText);
/// <summary>
/// Configures application to use specified implementation of <see cref="IConsole"/>.

13
CliFx/Internal/Guards.cs Normal file
View File

@@ -0,0 +1,13 @@
using System;
namespace CliFx.Internal
{
internal static class Guards
{
public static T GuardNotNull<T>(this T o, string argName = null) where T : class =>
o ?? throw new ArgumentNullException(argName);
public static int GuardNotZero(this int i, string argName = null) =>
i != 0 ? i : throw new ArgumentException("Cannot be zero.", argName);
}
}

View File

@@ -1,4 +1,6 @@
namespace CliFx.Models
using CliFx.Internal;
namespace CliFx.Models
{
/// <summary>
/// Metadata associated with an application.
@@ -25,9 +27,9 @@
/// </summary>
public ApplicationMetadata(string title, string executableName, string versionText)
{
Title = title;
ExecutableName = executableName;
VersionText = versionText;
Title = title.GuardNotNull(nameof(title));
ExecutableName = executableName.GuardNotNull(nameof(executableName));
VersionText = versionText.GuardNotNull(nameof(versionText));
}
}
}

View File

@@ -11,6 +11,7 @@ namespace CliFx.Models
{
/// <summary>
/// Specified command name.
/// Can be null if command was not specified.
/// </summary>
public string CommandName { get; }
@@ -24,8 +25,8 @@ namespace CliFx.Models
/// </summary>
public CommandInput(string commandName, IReadOnlyList<CommandOptionInput> options)
{
CommandName = commandName;
Options = options;
CommandName = commandName; // can be null
Options = options.GuardNotNull(nameof(options));
}
/// <summary>

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Text;
using CliFx.Internal;
namespace CliFx.Models
{
@@ -23,8 +24,8 @@ namespace CliFx.Models
/// </summary>
public CommandOptionInput(string alias, IReadOnlyList<string> values)
{
Alias = alias;
Values = values;
Alias = alias.GuardNotNull(nameof(alias));
Values = values.GuardNotNull(nameof(values));
}
/// <summary>

View File

@@ -45,12 +45,12 @@ namespace CliFx.Models
public CommandOptionSchema(PropertyInfo property, string name, char? shortName,
string groupName, bool isRequired, string description)
{
Property = property;
Name = name;
ShortName = shortName;
Property = property; // can be null
Name = name; // can be null
ShortName = shortName; // can be null
IsRequired = isRequired;
GroupName = groupName;
Description = description;
GroupName = groupName; // can be null
Description = description; // can be null
}
/// <inheritdoc />

View File

@@ -35,10 +35,10 @@ namespace CliFx.Models
/// </summary>
public CommandSchema(Type type, string name, string description, IReadOnlyList<CommandOptionSchema> options)
{
Type = type;
Name = name;
Description = description;
Options = options;
Type = type; // can be null
Name = name; // can be null
Description = description; // can be null
Options = options.GuardNotNull(nameof(options));
}
/// <inheritdoc />

View File

@@ -13,13 +13,19 @@ namespace CliFx.Models
/// <summary>
/// Gets whether a command was specified in the input.
/// </summary>
public static bool IsCommandSpecified(this CommandInput commandInput) => !commandInput.CommandName.IsNullOrWhiteSpace();
public static bool IsCommandSpecified(this CommandInput commandInput)
{
commandInput.GuardNotNull(nameof(commandInput));
return !commandInput.CommandName.IsNullOrWhiteSpace();
}
/// <summary>
/// Gets whether help was requested in the input.
/// </summary>
public static bool IsHelpRequested(this CommandInput commandInput)
{
commandInput.GuardNotNull(nameof(commandInput));
var firstOptionAlias = commandInput.Options.FirstOrDefault()?.Alias;
return string.Equals(firstOptionAlias, "help", StringComparison.OrdinalIgnoreCase) ||
@@ -32,6 +38,8 @@ namespace CliFx.Models
/// </summary>
public static bool IsVersionRequested(this CommandInput commandInput)
{
commandInput.GuardNotNull(nameof(commandInput));
var firstOptionAlias = commandInput.Options.FirstOrDefault()?.Alias;
return string.Equals(firstOptionAlias, "version", StringComparison.OrdinalIgnoreCase);
@@ -40,19 +48,28 @@ namespace CliFx.Models
/// <summary>
/// Gets whether this command is the default command, i.e. without a name.
/// </summary>
public static bool IsDefault(this CommandSchema commandSchema) => commandSchema.Name.IsNullOrWhiteSpace();
public static bool IsDefault(this CommandSchema commandSchema)
{
commandSchema.GuardNotNull(nameof(commandSchema));
return commandSchema.Name.IsNullOrWhiteSpace();
}
/// <summary>
/// Finds a command that has specified name, or null if not found.
/// </summary>
public static CommandSchema FindByName(this IReadOnlyList<CommandSchema> commandSchemas, string commandName) =>
commandSchemas.FirstOrDefault(c => string.Equals(c.Name, commandName, StringComparison.OrdinalIgnoreCase));
public static CommandSchema FindByName(this IReadOnlyList<CommandSchema> commandSchemas, string commandName)
{
commandSchemas.GuardNotNull(nameof(commandSchemas));
return commandSchemas.FirstOrDefault(c => string.Equals(c.Name, commandName, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Finds parent command to the command that has specified name, or null if not found.
/// </summary>
public static CommandSchema FindParent(this IReadOnlyList<CommandSchema> commandSchemas, string commandName)
{
commandSchemas.GuardNotNull(nameof(commandSchemas));
// If command has no name, it's the default command so it doesn't have a parent
if (commandName.IsNullOrWhiteSpace())
return null;
@@ -77,6 +94,9 @@ namespace CliFx.Models
/// </summary>
public static CommandOptionSchema FindByAlias(this IReadOnlyList<CommandOptionSchema> optionSchemas, string alias)
{
optionSchemas.GuardNotNull(nameof(optionSchemas));
alias.GuardNotNull(nameof(alias));
foreach (var optionSchema in optionSchemas)
{
// Compare against name. Case is ignored.

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using CliFx.Internal;
namespace CliFx.Models
{
@@ -29,9 +30,9 @@ namespace CliFx.Models
IReadOnlyList<CommandSchema> availableCommandSchemas,
CommandSchema targetCommandSchema)
{
ApplicationMetadata = applicationMetadata;
AvailableCommandSchemas = availableCommandSchemas;
TargetCommandSchema = targetCommandSchema;
ApplicationMetadata = applicationMetadata.GuardNotNull(nameof(applicationMetadata));
AvailableCommandSchemas = availableCommandSchemas.GuardNotNull(nameof(availableCommandSchemas));
TargetCommandSchema = targetCommandSchema.GuardNotNull(nameof(targetCommandSchema));
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using CliFx.Internal;
namespace CliFx.Services
{
@@ -8,6 +9,10 @@ namespace CliFx.Services
public class CommandFactory : ICommandFactory
{
/// <inheritdoc />
public ICommand CreateCommand(Type commandType) => (ICommand) Activator.CreateInstance(commandType);
public ICommand CreateCommand(Type commandType)
{
commandType.GuardNotNull(nameof(commandType));
return (ICommand) Activator.CreateInstance(commandType);
}
}
}

View File

@@ -14,6 +14,9 @@ namespace CliFx.Services
/// <inheritdoc />
public void RenderHelpText(IConsole console, HelpTextSource source)
{
console.GuardNotNull(nameof(console));
source.GuardNotNull(nameof(source));
// Track position
var column = 0;
var row = 0;

View File

@@ -19,7 +19,7 @@ namespace CliFx.Services
/// </summary>
public CommandInitializer(ICommandOptionInputConverter commandOptionInputConverter)
{
_commandOptionInputConverter = commandOptionInputConverter;
_commandOptionInputConverter = commandOptionInputConverter.GuardNotNull(nameof(commandOptionInputConverter));
}
/// <summary>
@@ -33,6 +33,10 @@ namespace CliFx.Services
/// <inheritdoc />
public void InitializeCommand(ICommand command, CommandSchema schema, CommandInput input)
{
command.GuardNotNull(nameof(command));
schema.GuardNotNull(nameof(schema));
input.GuardNotNull(nameof(input));
// Set command options
var isGroupNameDetected = false;
var groupName = default(string);

View File

@@ -15,6 +15,8 @@ namespace CliFx.Services
/// <inheritdoc />
public CommandInput ParseInput(IReadOnlyList<string> commandLineArguments)
{
commandLineArguments.GuardNotNull(nameof(commandLineArguments));
// Initialize command name placeholder
string commandName = null;

View File

@@ -20,7 +20,7 @@ namespace CliFx.Services
/// </summary>
public CommandOptionInputConverter(IFormatProvider formatProvider)
{
_formatProvider = formatProvider;
_formatProvider = formatProvider.GuardNotNull(nameof(formatProvider));
}
/// <summary>
@@ -244,6 +244,9 @@ namespace CliFx.Services
/// <inheritdoc />
public object ConvertOption(CommandOptionInput option, Type targetType)
{
option.GuardNotNull(nameof(option));
targetType.GuardNotNull(nameof(targetType));
if (targetType != typeof(string) && targetType.IsEnumerable())
{
var underlyingType = targetType.GetIEnumerableUnderlyingTypes().FirstOrDefault() ?? typeof(object);

View File

@@ -30,8 +30,7 @@ namespace CliFx.Services
/// <inheritdoc />
public CommandSchema GetCommandSchema(Type commandType)
{
if (!commandType.Implements(typeof(ICommand)))
throw new ArgumentException($"Command type must implement {nameof(ICommand)}.", nameof(commandType));
commandType.GuardNotNull(nameof(commandType));
var attribute = commandType.GetCustomAttribute<CommandAttribute>();

View File

@@ -1,4 +1,5 @@
using System;
using CliFx.Internal;
namespace CliFx.Services
{
@@ -14,10 +15,14 @@ namespace CliFx.Services
/// </summary>
public DelegateCommandFactory(Func<Type, ICommand> factoryMethod)
{
_factoryMethod = factoryMethod;
_factoryMethod = factoryMethod.GuardNotNull(nameof(factoryMethod));
}
/// <inheritdoc />
public ICommand CreateCommand(Type commandType) => _factoryMethod(commandType);
public ICommand CreateCommand(Type commandType)
{
commandType.GuardNotNull(nameof(commandType));
return _factoryMethod(commandType);
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using CliFx.Internal;
using CliFx.Models;
namespace CliFx.Services
@@ -14,13 +15,22 @@ namespace CliFx.Services
/// Resolves command schemas for commands of specified types.
/// </summary>
public static IReadOnlyList<CommandSchema> GetCommandSchemas(this ICommandSchemaResolver resolver,
IReadOnlyList<Type> commandTypes) => commandTypes.Select(resolver.GetCommandSchema).ToArray();
IReadOnlyList<Type> commandTypes)
{
resolver.GuardNotNull(nameof(resolver));
commandTypes.GuardNotNull(nameof(commandTypes));
return commandTypes.Select(resolver.GetCommandSchema).ToArray();
}
/// <summary>
/// Sets console foreground color, executes specified action, and sets the color back to the original value.
/// </summary>
public static void WithForegroundColor(this IConsole console, ConsoleColor foregroundColor, Action action)
{
console.GuardNotNull(nameof(console));
action.GuardNotNull(nameof(action));
var lastColor = console.ForegroundColor;
console.ForegroundColor = foregroundColor;
@@ -34,6 +44,9 @@ namespace CliFx.Services
/// </summary>
public static void WithBackgroundColor(this IConsole console, ConsoleColor backgroundColor, Action action)
{
console.GuardNotNull(nameof(console));
action.GuardNotNull(nameof(action));
var lastColor = console.BackgroundColor;
console.BackgroundColor = backgroundColor;
@@ -45,7 +58,12 @@ namespace CliFx.Services
/// <summary>
/// Sets console foreground and background colors, executes specified action, and sets the colors back to the original values.
/// </summary>
public static void WithColors(this IConsole console, ConsoleColor foregroundColor, ConsoleColor backgroundColor, Action action) =>
public static void WithColors(this IConsole console, ConsoleColor foregroundColor, ConsoleColor backgroundColor, Action action)
{
console.GuardNotNull(nameof(console));
action.GuardNotNull(nameof(action));
console.WithForegroundColor(foregroundColor, () => console.WithBackgroundColor(backgroundColor, action));
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using CliFx.Internal;
namespace CliFx.Services
{
@@ -39,9 +40,9 @@ namespace CliFx.Services
/// </summary>
public TestConsole(TextReader input, TextWriter output, TextWriter error)
{
Input = input;
Output = output;
Error = error;
Input = input.GuardNotNull(nameof(input));
Output = output.GuardNotNull(nameof(output));
Error = error.GuardNotNull(nameof(error));
}
/// <summary>