Skip to content

Commit 8e9ab4a

Browse files
committed
Data annotation attribute to check that a collection property does not have any duplicate items.
1 parent 98015b6 commit 8e9ab4a

File tree

2 files changed

+71
-17
lines changed

2 files changed

+71
-17
lines changed

CSharpExtender/Attributes/UniqueItemsAttribute.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ protected override ValidationResult IsValid(object value, ValidationContext vali
1717
return ValidationResult.Success;
1818
}
1919

20-
// Ensure the value is a collection
21-
// Need to explicitly check for a string as it is enumerable
20+
// Ensure the value is a collection.
21+
// Need to explicitly check for string, as it is enumerable.
2222
if (value is string || value is not IEnumerable collection)
2323
{
2424
return new ValidationResult("The UniqueItemsAttribute must be applied to a collection.");
@@ -37,24 +37,23 @@ protected override ValidationResult IsValid(object value, ValidationContext vali
3737
{
3838
if (AreItemsEqual(items[i], items[j]))
3939
{
40-
return new ValidationResult(ErrorMessage ??
41-
$"The list contains duplicate items at indices {i} and {j}.");
40+
// Use ErrorMessage if provided; otherwise, use default
41+
string errorMessage = ErrorMessage ??
42+
$"The list contains duplicate items at indices {i} and {j}.";
43+
return new ValidationResult(errorMessage);
4244
}
4345
}
4446
}
4547

4648
return ValidationResult.Success;
4749
}
4850

49-
private static bool AreItemsEqual(object item1, object item2)
51+
private bool AreItemsEqual(object item1, object item2)
5052
{
51-
// If both items are null, they are considered equal
5253
if (item1 == null && item2 == null)
5354
{
5455
return true;
5556
}
56-
57-
// If one is null and the other is not, they are not equal
5857
if (item1 == null || item2 == null)
5958
{
6059
return false;

Test.CSharpExtender/Attributes/Test_UniqueItemsAttribute.cs

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,26 @@ public void UniqueItemsAttribute_UniqueStrings_ReturnsSuccess()
4545
}
4646

4747
[Fact]
48-
public void UniqueItemsAttribute_DuplicateStrings_ReturnsError()
48+
public void UniqueItemsAttribute_DuplicateStrings_DefaultErrorMessage()
4949
{
5050
var model = new { Items = new List<string> { "apple", "apple", "cherry" } };
5151
var attribute = new UniqueItemsAttribute();
5252
var result = attribute.GetValidationResult(model.Items, GetValidationContext(model));
5353

5454
Assert.NotEqual(ValidationResult.Success, result);
5555
Assert.Contains("duplicate items", result.ErrorMessage);
56+
Assert.Contains("indices 0 and 1", result.ErrorMessage);
57+
}
58+
59+
[Fact]
60+
public void UniqueItemsAttribute_DuplicateStrings_CustomErrorMessage()
61+
{
62+
var model = new { Items = new List<string> { "apple", "apple", "cherry" } };
63+
var attribute = new UniqueItemsAttribute { ErrorMessage = "All items must be unique in the list." };
64+
var result = attribute.GetValidationResult(model.Items, GetValidationContext(model));
65+
66+
Assert.NotEqual(ValidationResult.Success, result);
67+
Assert.Equal("All items must be unique in the list.", result.ErrorMessage);
5668
}
5769

5870
[Fact]
@@ -66,14 +78,26 @@ public void UniqueItemsAttribute_UniqueNumbers_ReturnsSuccess()
6678
}
6779

6880
[Fact]
69-
public void UniqueItemsAttribute_DuplicateNumbers_ReturnsError()
81+
public void UniqueItemsAttribute_DuplicateNumbers_DefaultErrorMessage()
7082
{
7183
var model = new { Items = new List<int> { 1, 2, 2 } };
7284
var attribute = new UniqueItemsAttribute();
7385
var result = attribute.GetValidationResult(model.Items, GetValidationContext(model));
7486

7587
Assert.NotEqual(ValidationResult.Success, result);
7688
Assert.Contains("duplicate items", result.ErrorMessage);
89+
Assert.Contains("indices 1 and 2", result.ErrorMessage);
90+
}
91+
92+
[Fact]
93+
public void UniqueItemsAttribute_DuplicateNumbers_CustomErrorMessage()
94+
{
95+
var model = new { Items = new List<int> { 1, 2, 2 } };
96+
var attribute = new UniqueItemsAttribute { ErrorMessage = "Numbers must be unique." };
97+
var result = attribute.GetValidationResult(model.Items, GetValidationContext(model));
98+
99+
Assert.NotEqual(ValidationResult.Success, result);
100+
Assert.Equal("Numbers must be unique.", result.ErrorMessage);
77101
}
78102

79103
[Fact]
@@ -83,8 +107,8 @@ public void UniqueItemsAttribute_UniqueComplexObjects_ReturnsSuccess()
83107
{
84108
Items = new List<TestItem>
85109
{
86-
new(1, "apple"),
87-
new(2, "banana")
110+
new TestItem(1, "apple"),
111+
new TestItem(2, "banana")
88112
}
89113
};
90114

@@ -95,26 +119,46 @@ public void UniqueItemsAttribute_UniqueComplexObjects_ReturnsSuccess()
95119
}
96120

97121
[Fact]
98-
public void UniqueItemsAttribute_DuplicateComplexObjects_ReturnsError()
122+
public void UniqueItemsAttribute_DuplicateComplexObjects_DefaultErrorMessage()
99123
{
100124
var model = new
101125
{
102126
Items = new List<TestItem>
103127
{
104-
new(1, "apple"),
105-
new(1, "apple")
128+
new TestItem(1, "apple"),
129+
new TestItem(1, "apple")
106130
}
107131
};
108132

109133
var attribute = new UniqueItemsAttribute();
110134
var result = attribute.GetValidationResult(model.Items, GetValidationContext(model));
111-
135+
112136
Assert.NotEqual(ValidationResult.Success, result);
113137
Assert.Contains("duplicate items", result.ErrorMessage);
138+
Assert.Contains("indices 0 and 1", result.ErrorMessage);
114139
}
115140

116141
[Fact]
117-
public void UniqueItemsAttribute_NonCollection_ThrowsError()
142+
public void UniqueItemsAttribute_DuplicateComplexObjects_CustomErrorMessage()
143+
{
144+
var model = new
145+
{
146+
Items = new List<TestItem>
147+
{
148+
new TestItem(1, "apple"),
149+
new TestItem(1, "apple")
150+
}
151+
};
152+
153+
var attribute = new UniqueItemsAttribute { ErrorMessage = "Objects in the list must have unique values." };
154+
var result = attribute.GetValidationResult(model.Items, GetValidationContext(model));
155+
156+
Assert.NotEqual(ValidationResult.Success, result);
157+
Assert.Equal("Objects in the list must have unique values.", result.ErrorMessage);
158+
}
159+
160+
[Fact]
161+
public void UniqueItemsAttribute_SingleString_ReturnsError()
118162
{
119163
var model = new { Items = "not a list" };
120164
var attribute = new UniqueItemsAttribute();
@@ -123,4 +167,15 @@ public void UniqueItemsAttribute_NonCollection_ThrowsError()
123167
Assert.NotEqual(ValidationResult.Success, result);
124168
Assert.Contains("must be applied to a collection", result.ErrorMessage);
125169
}
170+
171+
[Fact]
172+
public void UniqueItemsAttribute_NonCollectionNonString_ReturnsError()
173+
{
174+
var model = new { Items = 42 }; // An int, not a string or collection
175+
var attribute = new UniqueItemsAttribute();
176+
var result = attribute.GetValidationResult(model.Items, GetValidationContext(model));
177+
178+
Assert.NotEqual(ValidationResult.Success, result);
179+
Assert.Contains("must be applied to a collection", result.ErrorMessage);
180+
}
126181
}

0 commit comments

Comments
 (0)