Skip to content

Commit fd4f6e6

Browse files
committed
feat: add required filters and assertions for fields
1 parent 4079331 commit fd4f6e6

16 files changed

Lines changed: 786 additions & 1 deletion

Docs/pages/02-filters.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ In addition to [access modifiers](#access-modifiers),
400400
| abstract / sealed *(properties only)* | `.WhichAreAbstract()` / `.WhichAreSealed()` | `.IsAbstract()` / `.IsSealed()` | `.AreAbstract()` / `.AreSealed()` |
401401
| virtual *(properties only)* | `.WhichAreVirtual()` | `.IsVirtual()` | `.AreVirtual()` |
402402
| override *(properties only)* | `.WhichOverride()` | `.Overrides()` | `.Override()` |
403-
| required *(properties only)* | `.WhichAreRequired()` | `.IsRequired()` | `.AreRequired()` |
403+
| required *(properties & fields)* | `.WhichAreRequired()` | `.IsRequired()` | `.AreRequired()` |
404404
| readable *(properties only)* | `.WhichAreReadable()` | `.IsReadable()` | `.AreReadable()` |
405405
| writable *(properties only)* | `.WhichAreWritable()` | `.IsWritable()` | `.AreWritable()` |
406406
| read-only *(properties only)* | `.WhichAreReadOnly()` | `.IsReadOnly()` | `.AreReadOnly()` |
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Reflection;
2+
using aweXpect.Reflection.Collections;
3+
using aweXpect.Reflection.Helpers;
4+
5+
namespace aweXpect.Reflection;
6+
7+
public static partial class FieldFilters
8+
{
9+
/// <summary>
10+
/// Filters for fields that are required.
11+
/// </summary>
12+
public static Filtered.Fields WhichAreRequired(this Filtered.Fields @this)
13+
=> @this.Which(Filter.Prefix<FieldInfo>(
14+
field => field.IsRequired(),
15+
"required "));
16+
17+
/// <summary>
18+
/// Filters for fields that are not required.
19+
/// </summary>
20+
public static Filtered.Fields WhichAreNotRequired(this Filtered.Fields @this)
21+
=> @this.Which(Filter.Prefix<FieldInfo>(
22+
field => !field.IsRequired(),
23+
"non-required "));
24+
}

Source/aweXpect.Reflection/Helpers/FieldInfoHelpers.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,15 @@ public static bool HasAttribute(
128128

129129
return false;
130130
}
131+
132+
/// <summary>
133+
/// Checks if the <paramref name="fieldInfo" /> is required (marked with the <c>required</c> modifier).
134+
/// </summary>
135+
/// <remarks>
136+
/// A field is considered required if it carries the
137+
/// <c>System.Runtime.CompilerServices.RequiredMemberAttribute</c>.
138+
/// </remarks>
139+
public static bool IsRequired(this FieldInfo? fieldInfo)
140+
=> fieldInfo != null && fieldInfo.GetCustomAttributes(true)
141+
.Any(attribute => attribute.GetType().FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute");
131142
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System.Reflection;
2+
using System.Text;
3+
using aweXpect.Core;
4+
using aweXpect.Core.Constraints;
5+
using aweXpect.Reflection.Helpers;
6+
using aweXpect.Results;
7+
8+
namespace aweXpect.Reflection;
9+
10+
public static partial class ThatField
11+
{
12+
/// <summary>
13+
/// Verifies that the <see cref="FieldInfo" /> is required.
14+
/// </summary>
15+
public static AndOrResult<FieldInfo?, IThat<FieldInfo?>> IsRequired(
16+
this IThat<FieldInfo?> subject)
17+
=> new(subject.Get().ExpectationBuilder.AddConstraint((it, grammars)
18+
=> new IsRequiredConstraint(it, grammars)),
19+
subject);
20+
21+
/// <summary>
22+
/// Verifies that the <see cref="FieldInfo" /> is not required.
23+
/// </summary>
24+
public static AndOrResult<FieldInfo?, IThat<FieldInfo?>> IsNotRequired(
25+
this IThat<FieldInfo?> subject)
26+
=> new(subject.Get().ExpectationBuilder.AddConstraint((it, grammars)
27+
=> new IsRequiredConstraint(it, grammars).Invert()),
28+
subject);
29+
30+
private sealed class IsRequiredConstraint(string it, ExpectationGrammars grammars)
31+
: ConstraintResult.WithNotNullValue<FieldInfo?>(it, grammars),
32+
IValueConstraint<FieldInfo?>
33+
{
34+
public ConstraintResult IsMetBy(FieldInfo? actual)
35+
{
36+
Actual = actual;
37+
Outcome = actual.IsRequired() ? Outcome.Success : Outcome.Failure;
38+
return this;
39+
}
40+
41+
protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
42+
=> stringBuilder.Append("is required");
43+
44+
protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
45+
{
46+
stringBuilder.Append(It).Append(" was non-required ");
47+
Formatter.Format(stringBuilder, Actual);
48+
}
49+
50+
protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
51+
=> stringBuilder.Append("is not required");
52+
53+
protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
54+
{
55+
stringBuilder.Append(It).Append(" was required ");
56+
Formatter.Format(stringBuilder, Actual);
57+
}
58+
}
59+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System.Collections.Generic;
2+
using System.Reflection;
3+
using System.Text;
4+
using aweXpect.Core;
5+
using aweXpect.Core.Constraints;
6+
using aweXpect.Reflection.Helpers;
7+
using aweXpect.Results;
8+
#if NET8_0_OR_GREATER
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
#endif
12+
13+
// ReSharper disable PossibleMultipleEnumeration
14+
15+
namespace aweXpect.Reflection;
16+
17+
public static partial class ThatFields
18+
{
19+
/// <summary>
20+
/// Verifies that all items in the filtered collection of <see cref="FieldInfo" /> are required.
21+
/// </summary>
22+
public static AndOrResult<IEnumerable<FieldInfo?>, IThat<IEnumerable<FieldInfo?>>> AreRequired(
23+
this IThat<IEnumerable<FieldInfo?>> subject)
24+
=> new(subject.Get().ExpectationBuilder.AddConstraint<IEnumerable<FieldInfo?>>((it, grammars)
25+
=> new AreRequiredConstraint(it, grammars)),
26+
subject);
27+
28+
#if NET8_0_OR_GREATER
29+
/// <summary>
30+
/// Verifies that all items in the filtered collection of <see cref="FieldInfo" /> are required.
31+
/// </summary>
32+
public static AndOrResult<IAsyncEnumerable<FieldInfo?>, IThat<IAsyncEnumerable<FieldInfo?>>> AreRequired(
33+
this IThat<IAsyncEnumerable<FieldInfo?>> subject)
34+
=> new(subject.Get().ExpectationBuilder.AddConstraint<IAsyncEnumerable<FieldInfo?>>((it, grammars)
35+
=> new AreRequiredConstraint(it, grammars)),
36+
subject);
37+
#endif
38+
39+
/// <summary>
40+
/// Verifies that all items in the filtered collection of <see cref="FieldInfo" /> are not required.
41+
/// </summary>
42+
public static AndOrResult<IEnumerable<FieldInfo?>, IThat<IEnumerable<FieldInfo?>>> AreNotRequired(
43+
this IThat<IEnumerable<FieldInfo?>> subject)
44+
=> new(subject.Get().ExpectationBuilder.AddConstraint<IEnumerable<FieldInfo?>>((it, grammars)
45+
=> new AreNotRequiredConstraint(it, grammars)),
46+
subject);
47+
48+
#if NET8_0_OR_GREATER
49+
/// <summary>
50+
/// Verifies that all items in the filtered collection of <see cref="FieldInfo" /> are not required.
51+
/// </summary>
52+
public static AndOrResult<IAsyncEnumerable<FieldInfo?>, IThat<IAsyncEnumerable<FieldInfo?>>> AreNotRequired(
53+
this IThat<IAsyncEnumerable<FieldInfo?>> subject)
54+
=> new(subject.Get().ExpectationBuilder.AddConstraint<IAsyncEnumerable<FieldInfo?>>((it, grammars)
55+
=> new AreNotRequiredConstraint(it, grammars)),
56+
subject);
57+
#endif
58+
59+
private sealed class AreRequiredConstraint(string it, ExpectationGrammars grammars)
60+
: CollectionConstraintResult<FieldInfo?>(grammars),
61+
IValueConstraint<IEnumerable<FieldInfo?>>
62+
#if NET8_0_OR_GREATER
63+
, IAsyncConstraint<IAsyncEnumerable<FieldInfo?>>
64+
#endif
65+
{
66+
#if NET8_0_OR_GREATER
67+
public async Task<ConstraintResult> IsMetBy(IAsyncEnumerable<FieldInfo?> actual,
68+
CancellationToken cancellationToken)
69+
=> await SetAsyncValue(actual, field => field.IsRequired());
70+
#endif
71+
72+
public ConstraintResult IsMetBy(IEnumerable<FieldInfo?> actual)
73+
=> SetValue(actual, field => field.IsRequired());
74+
75+
protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
76+
=> stringBuilder.Append("are all required");
77+
78+
protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
79+
{
80+
stringBuilder.Append(it).Append(" contained non-required fields ");
81+
Formatter.Format(stringBuilder, NotMatching, FormattingOptions.Indented(indentation));
82+
}
83+
84+
protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
85+
=> stringBuilder.Append("are not all required");
86+
87+
protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
88+
{
89+
stringBuilder.Append(it).Append(" only contained required fields ");
90+
Formatter.Format(stringBuilder, Matching, FormattingOptions.Indented(indentation));
91+
}
92+
}
93+
94+
private sealed class AreNotRequiredConstraint(string it, ExpectationGrammars grammars)
95+
: CollectionConstraintResult<FieldInfo?>(grammars),
96+
IValueConstraint<IEnumerable<FieldInfo?>>
97+
#if NET8_0_OR_GREATER
98+
, IAsyncConstraint<IAsyncEnumerable<FieldInfo?>>
99+
#endif
100+
{
101+
#if NET8_0_OR_GREATER
102+
public async Task<ConstraintResult> IsMetBy(IAsyncEnumerable<FieldInfo?> actual,
103+
CancellationToken cancellationToken)
104+
=> await SetAsyncValue(actual, field => !field.IsRequired());
105+
#endif
106+
107+
public ConstraintResult IsMetBy(IEnumerable<FieldInfo?> actual)
108+
=> SetValue(actual, field => !field.IsRequired());
109+
110+
protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
111+
=> stringBuilder.Append("are all not required");
112+
113+
protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
114+
{
115+
stringBuilder.Append(it).Append(" contained required fields ");
116+
Formatter.Format(stringBuilder, NotMatching, FormattingOptions.Indented(indentation));
117+
}
118+
119+
protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
120+
=> stringBuilder.Append("also contain a required field");
121+
122+
protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
123+
{
124+
stringBuilder.Append(it).Append(" only contained non-required fields ");
125+
Formatter.Format(stringBuilder, Matching, FormattingOptions.Indented(indentation));
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)