Skip to content

Commit d421a93

Browse files
authored
Adds Flags support vi Enum.IsDefinedWithFlagsSupport
Adds support for flags when verifying enums.
1 parent f2c31c3 commit d421a93

File tree

6 files changed

+223
-18
lines changed

6 files changed

+223
-18
lines changed

src/projects/EnsureThat/Enforcers/EnumArg.cs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using EnsureThat.Internals;
23
using JetBrains.Annotations;
34

45
namespace EnsureThat.Enforcers
@@ -7,22 +8,23 @@ public sealed class EnumArg
78
{
89
/// <summary>
910
/// Confirms that the <paramref name="value"/> is defined in the enum <typeparamref name="T"/>.
10-
/// Note that just like `Enum.IsDefined`, `Flags` based enums may be valid combination of defined values, but if the combined value
11-
/// itself is not named an error will be raised. Avoid usage with `Flags` enums.
11+
/// Note that just like <see cref="Enum.IsDefined(Type, object)"/>,
12+
/// <see cref="FlagsAttribute"/> based enums may be valid combination of defined values, but if the combined value
13+
/// itself is not named an error will be raised. Avoid usage with <see cref="FlagsAttribute"/> enums.
1214
/// </summary>
1315
/// <example>
1416
/// Flags example:
1517
///
1618
/// [Flags]
17-
/// enum Abc{
19+
/// enum Abc {
1820
/// A = 1,
1921
/// B = 2,
2022
/// C = 4,
2123
/// AB = 3
2224
/// }
2325
///
2426
/// Abc.A | Abc.B IsDefined=true (due to Abc.AB)
25-
/// Abc.A | Abc.C IsDefined=false (A and C are both valid, the composite is valid due to `Flags` attribute, but the composite is not a named enum value
27+
/// Abc.A | Abc.C IsDefined=false (A and C are both valid, the composite is valid due to <see cref="FlagsAttribute"/> attribute, but the composite is not a named enum value
2628
/// </example>
2729
public T IsDefined<T>(T value, [InvokerParameterName] string paramName = null, OptsFn optsFn = null) where T : struct, Enum
2830
{
@@ -37,5 +39,25 @@ public T IsDefined<T>(T value, [InvokerParameterName] string paramName = null, O
3739

3840
return value;
3941
}
42+
43+
/// <summary>
44+
/// Confirms that the <paramref name="value"/> is defined in the enum <typeparamref name="T"/>.
45+
/// Supports <see cref="FlagsAttribute"/> attribute.
46+
/// </summary>
47+
public T IsDefinedWithFlagsSupport<T>(T value, [InvokerParameterName] string paramName = null, OptsFn optsFn = null) where T : struct, Enum
48+
{
49+
var isEnumDefined = EnumOf<T>.Contains(value);
50+
51+
if (!isEnumDefined)
52+
{
53+
throw Ensure.ExceptionFactory.ArgumentOutOfRangeException(
54+
string.Format(ExceptionMessages.Enum_IsValidEnum, value, EnumOf<T>.EnumType),
55+
paramName,
56+
value,
57+
optsFn);
58+
}
59+
60+
return value;
61+
}
4062
}
4163
}

src/projects/EnsureThat/Ensure.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static class Ensure
2222
public static BoolArg Bool { get; } = new BoolArg();
2323

2424
/// <summary>
25-
/// Ensures for enumerables.
25+
/// Ensures for enums.
2626
/// </summary>
2727
[NotNull]
2828
public static EnumArg Enum { get; } = new EnumArg();

src/projects/EnsureThat/EnsureArg.Enums.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,32 @@ public static partial class EnsureArg
88
{
99
/// <summary>
1010
/// Confirms that the <paramref name="value"/> is defined in the enum <typeparamref name="T"/>.
11-
/// Note that just like `Enum.IsDefined`, `Flags` based enums may be valid combination of defined values, but if the combined value
12-
/// itself is not named an error will be raised. Avoid usage with `Flags` enums.
11+
/// Note that just like <see cref="Enum.IsDefined(Type, object)"/>,
12+
/// <see cref="FlagsAttribute"/> based enums may be valid combination of defined values, but if the combined value
13+
/// itself is not named an error will be raised. Avoid usage with <see cref="FlagsAttribute"/> enums.
1314
/// </summary>
1415
/// <example>
1516
/// Flags example:
1617
///
1718
/// [Flags]
18-
/// enum Abc{
19+
/// enum Abc {
1920
/// A = 1,
2021
/// B = 2,
2122
/// C = 4,
2223
/// AB = 3
2324
/// }
2425
///
2526
/// Abc.A | Abc.B IsDefined=true (due to Abc.AB)
26-
/// Abc.A | Abc.C IsDefined=false (A and C are both valid, the composite is valid due to `Flags` attribute, but the composite is not a named enum value
27+
/// Abc.A | Abc.C IsDefined=false (A and C are both valid, the composite is valid due to <see cref="FlagsAttribute"/> attribute, but the composite is not a named enum value
2728
/// </example>
2829
public static T EnumIsDefined<T>(T value, [InvokerParameterName] string paramName = null, OptsFn optsFn = null) where T : struct, Enum
2930
=> Ensure.Enum.IsDefined(value, paramName, optsFn);
31+
32+
/// <summary>
33+
/// Confirms that the <paramref name="value"/> is defined in the enum <typeparamref name="T"/>.
34+
/// Supports <see cref="FlagsAttribute"/> attribute.
35+
/// </summary>
36+
public static T EnumIsDefinedWithFlagsSupport<T>(T value, [InvokerParameterName] string paramName = null, OptsFn optsFn = null) where T : struct, Enum
37+
=> Ensure.Enum.IsDefinedWithFlagsSupport(value, paramName, optsFn);
3038
}
3139
}

src/projects/EnsureThat/EnsureThatEnumExtensions.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,32 @@ public static class EnsureThatEnumExtensions
44
{
55
/// <summary>
66
/// Confirms that the <paramref name="param.Value"/> is defined in the enum <typeparamref name="T"/>.
7-
/// Note that just like `Enum.IsDefined`, `Flags` based enums may be valid combination of defined values, but if the combined value
8-
/// itself is not named an error will be raised. Avoid usage with `Flags` enums.
7+
/// Note that just like <see cref="System.Enum.IsDefined(System.Type, object)"/>,
8+
/// <see cref="System.FlagsAttribute"/> based enums may be valid combination of defined values, but if the combined value
9+
/// itself is not named an error will be raised. Avoid usage with <see cref="System.FlagsAttribute"/> enums.
910
/// </summary>
1011
/// <example>
1112
/// Flags example:
1213
///
1314
/// [Flags]
14-
/// enum Abc{
15+
/// enum Abc {
1516
/// A = 1,
1617
/// B = 2,
1718
/// C = 4,
1819
/// AB = 3
1920
/// }
2021
///
2122
/// Abc.A | Abc.B IsDefined=true (due to Abc.AB)
22-
/// Abc.A | Abc.C IsDefined=false (A and C are both valid, the composite is valid due to `Flags` attribute, but the composite is not a named enum value
23+
/// Abc.A | Abc.C IsDefined=false (A and C are both valid, the composite is valid due to `<see cref="System.FlagsAttribute"/> attribute, but the composite is not a named enum value
2324
/// </example>
2425
public static void IsDefined<T>(this in Param<T> param) where T : struct, System.Enum
2526
=> Ensure.Enum.IsDefined(param.Value, param.Name, param.OptsFn);
27+
28+
/// <summary>
29+
/// Confirms that the <paramref name="param.Value"/> is defined in the enum <typeparamref name="T"/>.
30+
/// Supports <see cref="System.FlagsAttribute"/> attribute.
31+
/// </summary>
32+
public static void IsDefinedWithFlagsSupport<T>(this in Param<T> param) where T : struct, System.Enum
33+
=> Ensure.Enum.IsDefinedWithFlagsSupport(param.Value, param.Name, param.OptsFn);
2634
}
2735
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
6+
namespace EnsureThat.Internals
7+
{
8+
internal static class EnumOf<T> where T : struct, Enum
9+
{
10+
internal static readonly Type EnumType;
11+
private static readonly bool _hasFlags;
12+
private static readonly List<ulong> _values;
13+
14+
static EnumOf()
15+
{
16+
EnumType = typeof(T);
17+
18+
_hasFlags = EnumType.GetTypeInfo().GetCustomAttributes<FlagsAttribute>(false).Any();
19+
20+
var enumValues = Enum.GetValues(EnumType);
21+
_values = new List<ulong>(enumValues.Length);
22+
foreach (var v in enumValues)
23+
_values.Add(Convert.ToUInt64(v));
24+
}
25+
26+
internal static bool Contains(T value)
27+
{
28+
if (!_hasFlags)
29+
return Enum.IsDefined(EnumType, value);
30+
31+
var raw = Convert.ToUInt64(value);
32+
if (raw == 0)
33+
return Enum.IsDefined(EnumType, value);
34+
35+
ulong sum = 0;
36+
37+
foreach (var val in _values)
38+
{
39+
if ((raw & val) == val)
40+
sum |= val;
41+
}
42+
43+
return sum == raw;
44+
}
45+
}
46+
}

src/tests/UnitTests/EnsureEnumParamTests.cs

Lines changed: 126 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,53 @@ public class EnsureEnumParamTests : UnitTestBase
99
[Fact]
1010
public void IsDefined_ShouldNotThrow()
1111
{
12-
var item = Only1IsValid.Valid;
12+
var item = Only1IsValidEnum.Valid;
13+
14+
ShouldNotThrow(
15+
() => Ensure.Enum.IsDefined(item, ParamName),
16+
() => EnsureArg.EnumIsDefined(item, ParamName),
17+
() => Ensure.That(item, ParamName).IsDefined());
18+
}
19+
20+
[Theory]
21+
[InlineData((Only1IsValidEnum)2)]
22+
[InlineData((Only1IsValidEnum)0)]
23+
public void NotDefined_ShouldThrow(Only1IsValidEnum item)
24+
{
25+
ShouldThrow<ArgumentOutOfRangeException>(
26+
string.Format(ExceptionMessages.Enum_IsValidEnum, item, typeof(Only1IsValidEnum)),
27+
() => Ensure.Enum.IsDefined(item, ParamName),
28+
() => EnsureArg.EnumIsDefined(item, ParamName),
29+
() => Ensure.That(item, ParamName).IsDefined());
30+
}
31+
32+
[Fact]
33+
public void IsDefinedWithFlagsSupport_ShouldNotThrow()
34+
{
35+
var item = Only1IsValidEnum.Valid;
36+
37+
ShouldNotThrow(
38+
() => Ensure.Enum.IsDefinedWithFlagsSupport(item, ParamName),
39+
() => EnsureArg.EnumIsDefinedWithFlagsSupport(item, ParamName),
40+
() => Ensure.That(item, ParamName).IsDefinedWithFlagsSupport());
41+
}
42+
43+
[Theory]
44+
[InlineData((Only1IsValidEnum)2)]
45+
[InlineData((Only1IsValidEnum)0)]
46+
public void NotDefined_Extended_ShouldThrow(Only1IsValidEnum item)
47+
{
48+
ShouldThrow<ArgumentOutOfRangeException>(
49+
string.Format(ExceptionMessages.Enum_IsValidEnum, item, typeof(Only1IsValidEnum)),
50+
() => Ensure.Enum.IsDefinedWithFlagsSupport(item, ParamName),
51+
() => EnsureArg.EnumIsDefinedWithFlagsSupport(item, ParamName),
52+
() => Ensure.That(item, ParamName).IsDefinedWithFlagsSupport());
53+
}
54+
55+
[Fact]
56+
public void FlagIsDefined_ShouldNotThrow_IfNotCombined()
57+
{
58+
var item = TestFlagsEnum.Bar;
1359

1460
ShouldNotThrow(
1561
() => Ensure.Enum.IsDefined(item, ParamName),
@@ -18,20 +64,95 @@ public void IsDefined_ShouldNotThrow()
1864
}
1965

2066
[Fact]
21-
public void IsNotDefined_ShouldThrow()
67+
public void FlagIsDefined_ShouldThrow_IfCombined()
2268
{
23-
var item = (Only1IsValid)2;
69+
var item = TestFlagsEnum.Bar | TestFlagsEnum.Baz;
2470

2571
ShouldThrow<ArgumentOutOfRangeException>(
26-
string.Format(ExceptionMessages.Enum_IsValidEnum, item, typeof(Only1IsValid)),
72+
string.Format(ExceptionMessages.Enum_IsValidEnum, item, typeof(TestFlagsEnum)),
2773
() => Ensure.Enum.IsDefined(item, ParamName),
2874
() => EnsureArg.EnumIsDefined(item, ParamName),
2975
() => Ensure.That(item, ParamName).IsDefined());
3076
}
3177

32-
private enum Only1IsValid
78+
[Fact]
79+
public void FlagNotDefined_ShouldThrow()
80+
{
81+
var item = (TestFlagsEnum)3;
82+
ShouldThrow<ArgumentOutOfRangeException>(
83+
string.Format(ExceptionMessages.Enum_IsValidEnum, item, typeof(TestFlagsEnum)),
84+
() => Ensure.Enum.IsDefined(item, ParamName),
85+
() => EnsureArg.EnumIsDefined(item, ParamName),
86+
() => Ensure.That(item, ParamName).IsDefined());
87+
}
88+
89+
[Theory]
90+
[InlineData(TestFlagsEnum.Bar)]
91+
[InlineData(TestFlagsEnum.Bar | TestFlagsEnum.Baz)]
92+
public void FlagIsDefined_Extended_ShouldNotThrow(TestFlagsEnum item)
93+
{
94+
ShouldNotThrow(
95+
() => Ensure.Enum.IsDefinedWithFlagsSupport(item, ParamName),
96+
() => EnsureArg.EnumIsDefinedWithFlagsSupport(item, ParamName),
97+
() => Ensure.That(item, ParamName).IsDefinedWithFlagsSupport());
98+
}
99+
100+
[Theory]
101+
[InlineData((TestFlagsEnum)4)]
102+
[InlineData((TestFlagsEnum)0)]
103+
public void FlagNotDefined_Extended_ShouldThrow(TestFlagsEnum item)
104+
{
105+
ShouldThrow<ArgumentOutOfRangeException>(
106+
string.Format(ExceptionMessages.Enum_IsValidEnum, item, typeof(TestFlagsEnum)),
107+
() => Ensure.Enum.IsDefinedWithFlagsSupport(item, ParamName),
108+
() => EnsureArg.EnumIsDefinedWithFlagsSupport(item, ParamName),
109+
() => Ensure.That(item, ParamName).IsDefinedWithFlagsSupport());
110+
}
111+
112+
[Theory]
113+
[InlineData(TestFlagsOfWhateverPower.A)]
114+
[InlineData(TestFlagsOfWhateverPower.B)]
115+
[InlineData(TestFlagsOfWhateverPower.C)]
116+
[InlineData(TestFlagsOfWhateverPower.A | TestFlagsOfWhateverPower.C)]
117+
[InlineData(TestFlagsOfWhateverPower.B | TestFlagsOfWhateverPower.C)]
118+
public void FlagOfWhateverPowerIsDefined_ShouldNotThrow(TestFlagsOfWhateverPower item)
119+
{
120+
ShouldNotThrow(
121+
() => Ensure.Enum.IsDefinedWithFlagsSupport(item, ParamName),
122+
() => EnsureArg.EnumIsDefinedWithFlagsSupport(item, ParamName),
123+
() => Ensure.That(item, ParamName).IsDefinedWithFlagsSupport());
124+
}
125+
126+
[Fact]
127+
public void FlagOfWhateverPowerIsNotDefined_ShouldThrow()
128+
{
129+
var item = (TestFlagsOfWhateverPower)9;
130+
131+
ShouldThrow<ArgumentOutOfRangeException>(
132+
string.Format(ExceptionMessages.Enum_IsValidEnum, item, typeof(TestFlagsOfWhateverPower)),
133+
() => Ensure.Enum.IsDefinedWithFlagsSupport(item, ParamName),
134+
() => EnsureArg.EnumIsDefinedWithFlagsSupport(item, ParamName),
135+
() => Ensure.That(item, ParamName).IsDefinedWithFlagsSupport());
136+
}
137+
138+
public enum Only1IsValidEnum : byte
33139
{
34140
Valid = 1
35141
}
142+
143+
[Flags]
144+
public enum TestFlagsEnum : byte
145+
{
146+
Bar = 1,
147+
Baz = 1 << 1
148+
}
149+
150+
[Flags]
151+
public enum TestFlagsOfWhateverPower : byte
152+
{
153+
A = 4,
154+
B = 5,
155+
C = 8
156+
}
36157
}
37158
}

0 commit comments

Comments
 (0)