mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Fix exception when converting option values to array when there's only one value
This commit is contained in:
@@ -214,6 +214,12 @@ namespace CliFx.Tests.Services
|
||||
new[] {47, 69}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"47"}),
|
||||
typeof(int[]),
|
||||
new[] {47}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"value1", "value3"}),
|
||||
typeof(TestEnum[]),
|
||||
@@ -270,6 +276,16 @@ namespace CliFx.Tests.Services
|
||||
typeof(int)
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"123", "456"}),
|
||||
typeof(int)
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option"),
|
||||
typeof(int)
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "123"),
|
||||
typeof(TestNonStringParseable)
|
||||
|
||||
@@ -36,8 +36,13 @@ namespace CliFx.Internal
|
||||
|
||||
public static bool Implements(this Type type, Type interfaceType) => type.GetInterfaces().Contains(interfaceType);
|
||||
|
||||
public static Type GetNullableUnderlyingType(this Type type) => Nullable.GetUnderlyingType(type);
|
||||
|
||||
public static Type GetEnumerableUnderlyingType(this Type type)
|
||||
{
|
||||
if (type.IsPrimitive)
|
||||
return null;
|
||||
|
||||
if (type == typeof(IEnumerable))
|
||||
return typeof(object);
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace CliFx.Services
|
||||
return Enum.Parse(targetType, value, true);
|
||||
|
||||
// Nullable
|
||||
var nullableUnderlyingType = Nullable.GetUnderlyingType(targetType);
|
||||
var nullableUnderlyingType = targetType.GetNullableUnderlyingType();
|
||||
if (nullableUnderlyingType != null)
|
||||
return !value.IsNullOrWhiteSpace() ? ConvertValue(value, nullableUnderlyingType) : null;
|
||||
|
||||
@@ -134,11 +134,11 @@ namespace CliFx.Services
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// An exception was thrown when trying to convert the value
|
||||
// Wrap and rethrow exceptions that occur when trying to convert the value
|
||||
throw new CliFxException($"Can't convert value [{value}] to type [{targetType}].", ex);
|
||||
}
|
||||
|
||||
// Couldn't find a way to convert the value
|
||||
// Throw if we can't find a way to convert the value
|
||||
throw new CliFxException($"Can't convert value [{value}] to type [{targetType}].");
|
||||
}
|
||||
|
||||
@@ -148,33 +148,49 @@ namespace CliFx.Services
|
||||
optionInput.GuardNotNull(nameof(optionInput));
|
||||
targetType.GuardNotNull(nameof(targetType));
|
||||
|
||||
// Single value
|
||||
if (optionInput.Values.Count <= 1)
|
||||
// Get the underlying type of IEnumerable<T> if it's implemented by the target type.
|
||||
// Ignore string type because it's IEnumerable<T> but we don't treat it as such.
|
||||
var enumerableUnderlyingType = targetType != typeof(string) ? targetType.GetEnumerableUnderlyingType() : null;
|
||||
|
||||
// Convert to a non-enumerable type
|
||||
if (enumerableUnderlyingType == null)
|
||||
{
|
||||
// Throw if provided with more than 1 value
|
||||
if (optionInput.Values.Count > 1)
|
||||
{
|
||||
throw new CliFxException(
|
||||
$"Can't convert a sequence of values [{optionInput.Values.JoinToString(", ")}] " +
|
||||
$"to non-enumerable type [{targetType}].");
|
||||
}
|
||||
|
||||
// Retrieve a single value and convert
|
||||
var value = optionInput.Values.SingleOrDefault();
|
||||
return ConvertValue(value, targetType);
|
||||
}
|
||||
// Multiple values
|
||||
// Convert to an enumerable type
|
||||
else
|
||||
{
|
||||
// Determine underlying type of elements inside the target collection type
|
||||
var underlyingType = targetType.GetEnumerableUnderlyingType() ?? typeof(object);
|
||||
// Convert values to the underlying enumerable type and cast it to dynamic array
|
||||
var convertedValues = optionInput.Values
|
||||
.Select(v => ConvertValue(v, enumerableUnderlyingType))
|
||||
.ToNonGenericArray(enumerableUnderlyingType);
|
||||
|
||||
// Convert values to that type
|
||||
var convertedValues = optionInput.Values.Select(v => ConvertValue(v, underlyingType)).ToNonGenericArray(underlyingType);
|
||||
// Get the type of produced array
|
||||
var convertedValuesType = convertedValues.GetType();
|
||||
|
||||
// Assignable from array of values (e.g. T[], IReadOnlyList<T>, IEnumerable<T>)
|
||||
// Try to assign the array (works for T[], IReadOnlyList<T>, IEnumerable<T>, etc)
|
||||
if (targetType.IsAssignableFrom(convertedValuesType))
|
||||
return convertedValues;
|
||||
|
||||
// Has a constructor that accepts an array of values (e.g. HashSet<T>, List<T>)
|
||||
// Try to inject the array into the constructor (works for HashSet<T>, List<T>, etc)
|
||||
var arrayConstructor = targetType.GetConstructor(new[] {convertedValuesType});
|
||||
if (arrayConstructor != null)
|
||||
return arrayConstructor.Invoke(new object[] {convertedValues});
|
||||
|
||||
// Throw if we can't find a way to convert the values
|
||||
throw new CliFxException(
|
||||
$"Can't convert sequence of values [{optionInput.Values.JoinToString(", ")}] to type [{targetType}].");
|
||||
$"Can't convert a sequence of values [{optionInput.Values.JoinToString(", ")}] " +
|
||||
$"to type [{targetType}].");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user