Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions src/Ardalis.Specification/Internals/OneOrMany.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Ardalis.Specification;

internal struct OneOrMany<T>
internal struct OneOrMany<T> where T : class
{
private object? _value;

Expand All @@ -24,8 +24,47 @@ public void Add(T item)
if (_value is T singleValue)
{
_value = new List<T>(2) { singleValue, item };
}
}

public void AddSorted(T item, IComparer<T> comparer)
{
if (_value is null)
{
_value = item;
return;
}

if (comparer is null)
{
throw new ArgumentNullException(nameof(comparer), "Comparer cannot be null.");
}

if (_value is List<T> list)
{
var index = list.FindIndex(x => comparer.Compare(item, x) <= 0);
if (index == -1)
{
list.Add(item);
}
else
{
list.Insert(index, item);
}
return;
}

if (_value is T singleValue)
{
if (comparer.Compare(item, singleValue) <= 0)
{
_value = new List<T>(2) { item, singleValue };
}
else
{
_value = new List<T>(2) { singleValue, item };
}
}
}

public readonly T Single
Expand All @@ -41,6 +80,19 @@ public readonly T Single
}
}

public readonly T? SingleOrDefault
{
get
{
if (_value is T singleValue)
{
return singleValue;
}

return null;
}
}

public readonly IEnumerable<T> Values
{
get
Expand All @@ -50,9 +102,9 @@ public readonly IEnumerable<T> Values
return Enumerable.Empty<T>();
}

if (_value is List<T> tags)
if (_value is List<T> list)
{
return tags;
return list;
}

if (_value is T singleValue)
Expand Down
113 changes: 113 additions & 0 deletions tests/Ardalis.Specification.Tests/Internals/OneOrManyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,93 @@ public void Add_DoesNothing_GivenInvalidState()
value.Should().BeEquivalentTo(new string[] { "foo", "bar" });
}

[Fact]
public void AddSorted_CreatesSingleItem_GivenEmptyStruct()
{
var oneOrMany = new OneOrMany<string>();
oneOrMany.AddSorted("foo", Comparer<string>.Default);

var value = Accessors.ValueOf(ref oneOrMany);
value.Should().BeOfType<string>();
value.Should().Be("foo");
}

[Fact]
public void AddSorted_InsertsInPosition_GivenSingleItem()
{
var oneOrMany = new OneOrMany<string>();
Accessors.ValueOf(ref oneOrMany) = "foo";

oneOrMany.AddSorted("bar", Comparer<string>.Default);

var value = Accessors.ValueOf(ref oneOrMany);
value.Should().BeOfType<List<string>>();
value.Should().BeEquivalentTo(new List<string> { "bar", "foo" });
}

[Fact]
public void AddSorted_AddsToTheEnd_GivenSingleItem()
{
var oneOrMany = new OneOrMany<string>();
Accessors.ValueOf(ref oneOrMany) = "bar";

oneOrMany.AddSorted("foo", Comparer<string>.Default);

var value = Accessors.ValueOf(ref oneOrMany);
value.Should().BeOfType<List<string>>();
value.Should().BeEquivalentTo(new List<string> { "bar", "foo" });
}

[Fact]
public void AddSorted_InsertsInPosition_GivenTwoItems()
{
var oneOrMany = new OneOrMany<string>();
Accessors.ValueOf(ref oneOrMany) = new List<string> { "bar", "foo" };

oneOrMany.AddSorted("baz", Comparer<string>.Default);

var value = Accessors.ValueOf(ref oneOrMany);
value.Should().BeOfType<List<string>>();
value.Should().BeEquivalentTo(new List<string> { "bar", "baz", "foo" });
}

[Fact]
public void AddSorted_AddsToTheEnd_GivenTwoItems()
{
var oneOrMany = new OneOrMany<string>();
Accessors.ValueOf(ref oneOrMany) = new List<string> { "bar", "baz" };

oneOrMany.AddSorted("foo", Comparer<string>.Default);

var value = Accessors.ValueOf(ref oneOrMany);
value.Should().BeOfType<List<string>>();
value.Should().BeEquivalentTo(new List<string> { "bar", "baz", "foo" });
}

[Fact]
public void AddSorted_ThrowsArgumentNullException_GivenNullComparer()
{
var oneOrMany = new OneOrMany<string>();
Accessors.ValueOf(ref oneOrMany) = new List<string> { "bar", "baz" };

var action = () => oneOrMany.AddSorted("foo", null!);

action.Should().Throw<ArgumentNullException>();
}

[Fact]
public void AddSorted_DoesNothing_GivenInvalidState()
{
var oneOrMany = new OneOrMany<string>();
Accessors.ValueOf(ref oneOrMany) = new string[] { "foo", "bar" };

oneOrMany.AddSorted("baz", Comparer<string>.Default);

var value = Accessors.ValueOf(ref oneOrMany);
value.Should().BeOfType<string[]>();
value.Should().BeEquivalentTo(new string[] { "foo", "bar" });
}

[Fact]
public void Single_ReturnsSingleItem_GivenSingleItem()
{
Expand Down Expand Up @@ -125,6 +212,32 @@ public void Single_Throws_GivenMultipleItems()
action.Should().Throw<InvalidOperationException>();
}

[Fact]
public void SingleOrDefault_ReturnsSingleItem_GivenSingleItem()
{
var oneOrMany = new OneOrMany<string>();
Accessors.ValueOf(ref oneOrMany) = "foo";

oneOrMany.SingleOrDefault.Should().Be("foo");
}

[Fact]
public void SingleOrDefault_ReturnsDefault_GivenEmptyStruct()
{
var oneOrMany = new OneOrMany<string>();

oneOrMany.SingleOrDefault.Should().BeNull();
}

[Fact]
public void SingleOrDefault_ReturnsDefault_GivenMultipleItems()
{
var oneOrMany = new OneOrMany<string>();
Accessors.ValueOf(ref oneOrMany) = new string[] { "foo", "bar" };

oneOrMany.SingleOrDefault.Should().BeNull();
}

[Fact]
public void Values_ReturnsEmpty_GivenEmptyStruct()
{
Expand Down