Resolve #785 - improve API for StringValuesModel
This means that the model: * Implements IConvertible so that Jint may implicitly convert it to other types as if it were just a string. * The Values & Values properties are perhaps a little less confusing now, and present a more consistent set of state, regardless of the inner model. * There's a minor simplification to ToString, since String.Join already did what we wanted for one or many values. Notable though is - because this class implements IConvertible, it is no longer CLS compliant. We do not currently mark this assembly as CLS complient so as things stand that's OK. I do not know if there are any plans/expectations for CLS compliance in Elsa. If we do plan to mark the assemblies as compliant then we must mark this class [CLSCompliant(false)].
This commit is contained in:
parent
a4ad46c51d
commit
a81d47afff
|
|
@ -1,33 +1,84 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Elsa.Activities.Http.Models
|
||||
{
|
||||
public class StringValuesModel
|
||||
/// <summary>
|
||||
/// Represents a collection of zero or more string values. It is used for values such as
|
||||
/// HTTP header/query string values. These are often thought-of as key/value pairs but might
|
||||
/// have more than one value.
|
||||
/// </summary>
|
||||
public class StringValuesModel : IConvertible
|
||||
{
|
||||
public string? Value { get; set; }
|
||||
public string[]? Values { get; set; }
|
||||
/// <summary>
|
||||
/// Gets either the first value of <see cref="Values"/> or a <see langword="null"/> reference
|
||||
/// if the values are null or empty.
|
||||
/// </summary>
|
||||
/// <returns>The first value</returns>
|
||||
public string? Value => Values?.FirstOrDefault();
|
||||
|
||||
public StringValuesModel()
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a collection of the string values for the current instance.
|
||||
/// </summary>
|
||||
/// <value>The values</value>
|
||||
public string[] Values { get; set; }
|
||||
|
||||
public StringValuesModel(StringValues value)
|
||||
{
|
||||
Value = value.Count == 1 ? value.ToString() : default;
|
||||
Values = value.Count != 1 ? value.ToArray() : default;
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="StringValuesModel"/> with an empty collection of values.
|
||||
/// </summary>
|
||||
public StringValuesModel() => Values = new string[0];
|
||||
|
||||
public override string? ToString()
|
||||
{
|
||||
if (Values == null)
|
||||
return Value;
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="StringValuesModel"/> from a <see cref="StringValues"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">A model of zero or more string values.</param>
|
||||
public StringValuesModel(StringValues value) => Values = value.ToArray();
|
||||
|
||||
return Values.Length switch
|
||||
{
|
||||
0 => default,
|
||||
1 => Values[0],
|
||||
_ => string.Join(",", Values)
|
||||
};
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a string representation of the current instance. Either null (for no, or an empty-collection of <see cref="Values"/>),
|
||||
/// a single string (for a collection of one <see cref="Values"/>) or a comma-separated string of many values.
|
||||
/// </summary>
|
||||
/// <returns>The string representation of the <see cref="Values"/>.</returns>
|
||||
public override string? ToString() => (Values?.Any() == true) ? string.Join(",", Values) : null;
|
||||
|
||||
#region IConvertible implementation
|
||||
|
||||
TypeCode IConvertible.GetTypeCode() => TypeCode.Object;
|
||||
|
||||
bool IConvertible.ToBoolean(IFormatProvider provider) => Convert.ToBoolean(ToString(), provider);
|
||||
|
||||
byte IConvertible.ToByte(IFormatProvider provider) => Convert.ToByte(ToString(), provider);
|
||||
|
||||
char IConvertible.ToChar(IFormatProvider provider) => Convert.ToChar(ToString(), provider);
|
||||
|
||||
DateTime IConvertible.ToDateTime(IFormatProvider provider) => Convert.ToDateTime(ToString(), provider);
|
||||
|
||||
decimal IConvertible.ToDecimal(IFormatProvider provider) => Convert.ToDecimal(ToString(), provider);
|
||||
|
||||
double IConvertible.ToDouble(IFormatProvider provider) => Convert.ToDouble(ToString(), provider);
|
||||
|
||||
short IConvertible.ToInt16(IFormatProvider provider) => Convert.ToInt16(ToString(), provider);
|
||||
|
||||
int IConvertible.ToInt32(IFormatProvider provider) => Convert.ToInt32(ToString(), provider);
|
||||
|
||||
long IConvertible.ToInt64(IFormatProvider provider) => Convert.ToInt64(ToString(), provider);
|
||||
|
||||
sbyte IConvertible.ToSByte(IFormatProvider provider) => Convert.ToSByte(ToString(), provider);
|
||||
|
||||
float IConvertible.ToSingle(IFormatProvider provider) => Convert.ToSingle(ToString(), provider);
|
||||
|
||||
string IConvertible.ToString(IFormatProvider provider) => ToString()!;
|
||||
|
||||
object IConvertible.ToType(Type conversionType, IFormatProvider provider) => Convert.ChangeType(ToString(), conversionType, provider);
|
||||
|
||||
ushort IConvertible.ToUInt16(IFormatProvider provider) => Convert.ToUInt16(ToString(), provider);
|
||||
|
||||
uint IConvertible.ToUInt32(IFormatProvider provider) => Convert.ToUInt32(ToString(), provider);
|
||||
|
||||
ulong IConvertible.ToUInt64(IFormatProvider provider) => Convert.ToUInt64(ToString(), provider);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using Elsa.Activities.Http.Models;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
||||
namespace Elsa.Activities.Http
|
||||
{
|
||||
public class StringValuesModelTests
|
||||
{
|
||||
[Theory(DisplayName = "It should be possible to use Convert.ChangeType to convert an instance to string. This is primarily to support usage via Jint."), AutoMoqData]
|
||||
public void ItShouldBePossibleToConvertAStringValuesModelToString(string[] values)
|
||||
{
|
||||
var sut = new StringValuesModel(new StringValues(values));
|
||||
var result = Convert.ChangeType(sut, typeof(string));
|
||||
Assert.Equal(sut.ToString(), result);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "It should be possible to use Convert.ToInt32 to convert an instance to a number if the value is a valid string representation of a number. This is primarily to support usage via Jint."), AutoMoqData]
|
||||
public void ItShouldBePossibleToConvertAStringValuesModelToInt32IfValid()
|
||||
{
|
||||
var sut = new StringValuesModel(new StringValues("3"));
|
||||
var result = Convert.ChangeType(sut, typeof(int));
|
||||
Assert.Equal(3, result);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "The Value property should return the first value if there is more than one."), AutoMoqData]
|
||||
public void ValueShouldReturnFirstItemIfThereAreMoreThanOne(string firstValue, string secondValue)
|
||||
{
|
||||
var sut = new StringValuesModel(new StringValues(new [] { firstValue, secondValue }));
|
||||
Assert.Equal(firstValue, sut.Value);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "The Value property should always return the first value if there is only one."), AutoMoqData]
|
||||
public void ValueShouldReturnFirstItemIfThereIsOne(string firstValue)
|
||||
{
|
||||
var sut = new StringValuesModel(new StringValues(new [] { firstValue }));
|
||||
Assert.Equal(firstValue, sut.Value);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "The Value property should return null if there are no values.")]
|
||||
public void ValueShouldReturnNullIfThereAreNone()
|
||||
{
|
||||
var sut = new StringValuesModel(new StringValues(new string[0]));
|
||||
Assert.Null(sut.Value);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "The Values property should return a length-one collection if there is a single value."), AutoMoqData]
|
||||
public void ValuesShouldReturnLengthOneCollectionIfThereIsOneValue(string firstValue)
|
||||
{
|
||||
var sut = new StringValuesModel(new StringValues(new [] { firstValue }));
|
||||
Assert.Equal(new [] {firstValue}, sut.Values);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "The Values property should return a length-two collection if there are two values."), AutoMoqData]
|
||||
public void ValuesShouldReturnLengthTwoCollectionIfThereAreTwoValues(string firstValue, string secondValue)
|
||||
{
|
||||
var sut = new StringValuesModel(new StringValues(new [] { firstValue, secondValue }));
|
||||
Assert.Equal(new [] {firstValue, secondValue}, sut.Values);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "The Values property should return an empty collection if there are no values.")]
|
||||
public void ValuesShouldReturnEmptyCollectionIfThereAreNone()
|
||||
{
|
||||
var sut = new StringValuesModel(new StringValues(new string[0]));
|
||||
Assert.Empty(sut.Values);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "The ToString method should return null if there are no values")]
|
||||
public void ToStringShouldReturnNullIfNotValues()
|
||||
{
|
||||
var sut = new StringValuesModel(new StringValues(new string[0]));
|
||||
Assert.Null(sut.ToString());
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "The ToString method should return the first value if there is only one"), AutoMoqData]
|
||||
public void ToStringShouldReturnFirstValueIfThereIsOnlyOne(string firstValue)
|
||||
{
|
||||
var sut = new StringValuesModel(new StringValues(new [] { firstValue }));
|
||||
Assert.Equal(firstValue, sut.ToString());
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "The ToString method should return comma-separated values if there are more than one"), AutoMoqData]
|
||||
public void ToStringShouldReturnCommaSeparatedValuesIfThereIsMoreThanOne(string firstValue, string secondValue)
|
||||
{
|
||||
var sut = new StringValuesModel(new StringValues(new [] { firstValue, secondValue }));
|
||||
Assert.Equal($"{firstValue},{secondValue}", sut.ToString());
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "The parameterless constructor should initialize Values to an empty collection")]
|
||||
public void ParameterlessCtorShouldInitializeWithEmptyCollection()
|
||||
{
|
||||
var sut = new StringValuesModel();
|
||||
Assert.Empty(sut.Values);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue