Throw when a required option is set but doesn't have a value

Closes #47
This commit is contained in:
Alexey Golub
2020-04-16 16:02:21 +03:00
parent fdd39855ad
commit 8c3b8d1f49
3 changed files with 34 additions and 16 deletions

View File

@@ -933,7 +933,7 @@ namespace CliFx.Tests
} }
[Fact] [Fact]
public void Property_annotated_as_a_required_option_must_always_be_bound_to_some_value() public void Property_annotated_as_a_required_option_must_always_be_set()
{ {
// Arrange // Arrange
var schema = ApplicationSchema.Resolve(new[] {typeof(RequiredOptionCommand)}); var schema = ApplicationSchema.Resolve(new[] {typeof(RequiredOptionCommand)});
@@ -946,6 +946,20 @@ namespace CliFx.Tests
Assert.Throws<CliFxException>(() => schema.InitializeEntryPoint(input)); Assert.Throws<CliFxException>(() => schema.InitializeEntryPoint(input));
} }
[Fact]
public void Property_annotated_as_a_required_option_must_always_be_bound_to_some_value()
{
// Arrange
var schema = ApplicationSchema.Resolve(new[] {typeof(RequiredOptionCommand)});
var input = new CommandLineInputBuilder()
.AddOption(nameof(RequiredOptionCommand.OptionB))
.Build();
// Act & assert
Assert.Throws<CliFxException>(() => schema.InitializeEntryPoint(input));
}
[Fact] [Fact]
public void Property_annotated_as_parameter_is_bound_directly_from_argument_value_according_to_the_order() public void Property_annotated_as_parameter_is_bound_directly_from_argument_value_according_to_the_order()
{ {

View File

@@ -97,15 +97,15 @@ namespace CliFx.Domain
var unsetRequiredOptions = Options.Where(o => o.IsRequired).ToList(); var unsetRequiredOptions = Options.Where(o => o.IsRequired).ToList();
// Environment variables // Environment variables
foreach (var environmentVariable in environmentVariables) foreach (var (name, value) in environmentVariables)
{ {
var option = Options.FirstOrDefault(o => o.MatchesEnvironmentVariableName(environmentVariable.Key)); var option = Options.FirstOrDefault(o => o.MatchesEnvironmentVariableName(name));
if (option != null) if (option != null)
{ {
var values = option.IsScalar var values = option.IsScalar
? new[] {environmentVariable.Value} ? new[] {value}
: environmentVariable.Value.Split(Path.PathSeparator); : value.Split(Path.PathSeparator);
option.Inject(command, values); option.Inject(command, values);
unsetRequiredOptions.Remove(option); unsetRequiredOptions.Remove(option);
@@ -122,12 +122,14 @@ namespace CliFx.Domain
if (inputs.Any()) if (inputs.Any())
{ {
option.Inject(command, inputs.SelectMany(i => i.Values).ToArray()); var inputValues = inputs.SelectMany(i => i.Values).ToArray();
option.Inject(command, inputValues);
foreach (var input in inputs) foreach (var input in inputs)
remainingOptionInputs.Remove(input); remainingOptionInputs.Remove(input);
unsetRequiredOptions.Remove(option); if (inputValues.Any())
unsetRequiredOptions.Remove(option);
} }
} }

View File

@@ -1,18 +1,20 @@
// ReSharper disable CheckNamespace // ReSharper disable CheckNamespace
#if NET45 || NETSTANDARD2_0 // Polyfills to bridge the missing APIs in older versions of the framework/standard.
using System.Collections.Generic;
using System.Text;
namespace CliFx.Internal #if NETSTANDARD2_0 || NET45
namespace System.Collections.Generic
{ {
internal static class Polyfills internal static class Extensions
{ {
public static TValue GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> self, TKey key) => public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> pair, out TKey key, out TValue value)
self.TryGetValue(key, out var value) ? value : default; {
key = pair.Key;
value = pair.Value;
}
public static StringBuilder AppendJoin<T>(this StringBuilder self, string separator, IEnumerable<T> items) => public static TValue GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dic, TKey key) =>
self.Append(string.Join(separator, items)); dic.TryGetValue(key, out var result) ? result! : default!;
} }
} }
#endif #endif