Skip to content

Commit 512f148

Browse files
committedJun 23, 2024
feat: can handle first name last name derivation
1 parent 8543e88 commit 512f148

12 files changed

+370
-41
lines changed
 

‎QueryKit.IntegrationTests/Tests/DatabaseFilteringTests.cs

+117-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
namespace QueryKit.IntegrationTests.Tests;
22

3-
using System.Linq.Expressions;
43
using Bogus;
54
using Configuration;
65
using FluentAssertions;
@@ -12,9 +11,9 @@ namespace QueryKit.IntegrationTests.Tests;
1211
using WebApiTestProject.Database;
1312
using WebApiTestProject.Entities;
1413
using WebApiTestProject.Entities.Recipes;
15-
using WebApiTestProject.Features;
14+
using Xunit.Abstractions;
1615

17-
public class DatabaseFilteringTests : TestBase
16+
public class DatabaseFilteringTests(ITestOutputHelper testOutputHelper) : TestBase
1817
{
1918
[Fact]
2019
public async Task can_filter_by_string()
@@ -40,6 +39,121 @@ public async Task can_filter_by_string()
4039
people[0].Id.Should().Be(fakePersonOne.Id);
4140
}
4241

42+
[Fact]
43+
public async Task can_filter_by_boolean()
44+
{
45+
// Arrange
46+
var testingServiceScope = new TestingServiceScope();
47+
var faker = new Faker();
48+
var fakePersonOne = new FakeTestingPersonBuilder()
49+
.WithTitle(faker.Lorem.Sentence())
50+
.WithFavorite(true)
51+
.Build();
52+
var fakePersonTwo = new FakeTestingPersonBuilder()
53+
.WithTitle(faker.Lorem.Sentence())
54+
.WithFavorite(false)
55+
.Build();
56+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
57+
58+
var input = $"""{nameof(TestingPerson.Title)} == "{fakePersonOne.Title}" && {nameof(TestingPerson.Favorite)} == true""";
59+
60+
// Act
61+
var queryablePeople = testingServiceScope.DbContext().People;
62+
var appliedQueryable = queryablePeople.ApplyQueryKitFilter(input);
63+
var people = await appliedQueryable.ToListAsync();
64+
65+
// Assert
66+
people.Count.Should().Be(1);
67+
people[0].Id.Should().Be(fakePersonOne.Id);
68+
}
69+
70+
[Fact]
71+
public async Task can_filter_by_combo_multi_value_pass()
72+
{
73+
// Arrange
74+
var testingServiceScope = new TestingServiceScope();
75+
var fakePersonOne = new FakeTestingPersonBuilder().Build();
76+
var fakePersonTwo = new FakeTestingPersonBuilder()
77+
.WithFirstName(fakePersonOne.FirstName)
78+
.Build();
79+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
80+
81+
var input = $"""fullname @=* "{fakePersonOne.FirstName} {fakePersonOne.LastName}" """;
82+
var config = new QueryKitConfiguration(config =>
83+
{
84+
config.DerivedProperty<TestingPerson>(tp => tp.FirstName + " " + tp.LastName).HasQueryName("fullname");
85+
});
86+
87+
// Act
88+
var queryablePeople = testingServiceScope.DbContext().People;
89+
var appliedQueryable = queryablePeople.ApplyQueryKitFilter(input, config);
90+
var people = await appliedQueryable.ToListAsync();
91+
92+
// Assert
93+
people.Count.Should().Be(1);
94+
people[0].Id.Should().Be(fakePersonOne.Id);
95+
}
96+
97+
[Fact]
98+
public async Task can_filter_by_combo_complex()
99+
{
100+
// Arrange
101+
var testingServiceScope = new TestingServiceScope();
102+
var fakePersonOne = new FakeTestingPersonBuilder()
103+
.WithAge(8888)
104+
.Build();
105+
var fakePersonTwo = new FakeTestingPersonBuilder()
106+
.WithFirstName(fakePersonOne.FirstName)
107+
.Build();
108+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
109+
110+
var input = $"""(fullname @=* "{fakePersonOne.FirstName} {fakePersonOne.LastName}") && age >= {fakePersonOne.Age}""";
111+
var config = new QueryKitConfiguration(config =>
112+
{
113+
config.DerivedProperty<TestingPerson>(tp => tp.FirstName + " " + tp.LastName).HasQueryName("fullname");
114+
});
115+
116+
// Act
117+
var queryablePeople = testingServiceScope.DbContext().People;
118+
var appliedQueryable = queryablePeople.ApplyQueryKitFilter(input, config);
119+
var people = await appliedQueryable.ToListAsync();
120+
121+
// Assert
122+
people.Count.Should().Be(1);
123+
people[0].Id.Should().Be(fakePersonOne.Id);
124+
}
125+
126+
[Fact]
127+
public async Task can_filter_by_combo()
128+
{
129+
// Arrange
130+
var testingServiceScope = new TestingServiceScope();
131+
var fakePersonOne = new FakeTestingPersonBuilder()
132+
.WithFirstName(Guid.NewGuid().ToString())
133+
.Build();
134+
var fakePersonTwo = new FakeTestingPersonBuilder().Build();
135+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
136+
137+
var input = $"""fullname @=* "{fakePersonOne.FirstName}" """;
138+
var config = new QueryKitConfiguration(config =>
139+
{
140+
config.DerivedProperty<TestingPerson>(tp => tp.FirstName + " " + tp.LastName).HasQueryName("fullname");
141+
});
142+
143+
// Act
144+
var queryablePeople = testingServiceScope.DbContext().People;
145+
var appliedQueryable = queryablePeople.ApplyQueryKitFilter(input, config);
146+
var people = await appliedQueryable.ToListAsync();
147+
// var people = testingServiceScope.DbContext().People
148+
// // .Where(p => (p.FirstName + " " + p.LastName).ToLower().Contains(fakePersonOne.FirstName.ToLower()))
149+
// // .Where(x => ((x.FirstName + " ") + x.LastName).ToLower().Contains("ito".ToLower()))
150+
// .ToList();
151+
152+
// Assert
153+
people.Count.Should().Be(1);
154+
people[0].Id.Should().Be(fakePersonOne.Id);
155+
}
156+
43157
[Fact]
44158
public async Task can_filter_by_string_for_collection()
45159
{

‎QueryKit.WebApiTestProject/Entities/TestingPerson.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ namespace QueryKit.WebApiTestProject.Entities;
22

33
public class TestingPerson
44
{
5-
public string? Title { get; set; }
5+
public string? Title { get; set; } = default!;
6+
public string? FirstName { get; set; } = default!;
7+
public string? LastName { get; set; } = default!;
68
public int? Age { get; set; }
79
public BirthMonthEnum? BirthMonth { get; set; }
810
public decimal? Rating { get; set; }

‎QueryKit.WebApiTestProject/Migrations/20240416001404_BaseTestingMigration.Designer.cs ‎QueryKit.WebApiTestProject/Migrations/20240501011638_BaseTestingMigration.Designer.cs

+9-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎QueryKit.WebApiTestProject/Migrations/20240416001404_BaseTestingMigration.cs ‎QueryKit.WebApiTestProject/Migrations/20240501011638_BaseTestingMigration.cs

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ protected override void Up(MigrationBuilder migrationBuilder)
2121
{
2222
id = table.Column<Guid>(type: "uuid", nullable: false),
2323
title = table.Column<string>(type: "text", nullable: true),
24+
first_name = table.Column<string>(type: "text", nullable: true),
25+
last_name = table.Column<string>(type: "text", nullable: true),
2426
age = table.Column<int>(type: "integer", nullable: true),
2527
birth_month = table.Column<int>(type: "integer", nullable: true),
2628
rating = table.Column<decimal>(type: "numeric", nullable: true),

‎QueryKit.WebApiTestProject/Migrations/TestingDbContextModelSnapshot.cs

+8
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ protected override void BuildModel(ModelBuilder modelBuilder)
203203
.HasColumnType("boolean")
204204
.HasColumnName("favorite");
205205

206+
b.Property<string>("FirstName")
207+
.HasColumnType("text")
208+
.HasColumnName("first_name");
209+
210+
b.Property<string>("LastName")
211+
.HasColumnType("text")
212+
.HasColumnName("last_name");
213+
206214
b.Property<decimal?>("Rating")
207215
.HasColumnType("numeric")
208216
.HasColumnName("rating");

‎QueryKit/Configuration/QueryKitSettings.cs

+5
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,9 @@ public QueryKitPropertyMapping<TModel> Property<TModel>(Expression<Func<TModel,
4040
{
4141
return PropertyMappings.Property(propertySelector);
4242
}
43+
44+
public QueryKitPropertyMapping<TModel> DerivedProperty<TModel>(Expression<Func<TModel, object>>? propertySelector)
45+
{
46+
return PropertyMappings.DerivedProperty(propertySelector);
47+
}
4348
}

‎QueryKit/FilterParser.cs

+41-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-

2-
namespace QueryKit;
1+
namespace QueryKit;
32

4-
using System.Collections;
53
using System.Globalization;
64
using System.Linq.Expressions;
75
using System.Reflection;
@@ -26,20 +24,34 @@ public static Expression<Func<T, bool>> ParseFilter<T>(string input, IQueryKitCo
2624
input = config?.PropertyMappings?.ReplaceAliasesWithPropertyPaths(input) ?? input;
2725

2826
var parameter = Expression.Parameter(typeof(T), "x");
29-
Expression expr;
27+
Expression expr;
3028
try
3129
{
3230
expr = ExprParser<T>(parameter, config).End().Parse(input);
31+
expr = ReplaceDerivedProperties(expr, config, parameter);
3332
}
34-
catch (InvalidOperationException e) {
33+
catch (InvalidOperationException e)
34+
{
3535
throw new ParsingException(e);
3636
}
3737
catch (ParseException e)
3838
{
3939
throw new ParsingException(e);
4040
}
41+
4142
return Expression.Lambda<Func<T, bool>>(expr, parameter);
4243
}
44+
45+
private static Expression ReplaceDerivedProperties(Expression expr, IQueryKitConfiguration? config, ParameterExpression parameter)
46+
{
47+
if (config?.PropertyMappings == null)
48+
{
49+
return expr;
50+
}
51+
52+
return new ParameterReplacer(parameter).Visit(expr);
53+
}
54+
4355

4456
private static readonly Parser<string> Identifier =
4557
from first in Parse.Letter.Once()
@@ -358,6 +370,20 @@ private static Expression CreateRightExprFromType(Type leftExprType, string righ
358370
var nullableCtor = rawType.GetConstructor(new[] {enumType});
359371
return Expression.New(nullableCtor, constant);
360372
}
373+
374+
// for some complex derived expressions
375+
if (targetType == typeof(object))
376+
{
377+
if (right == "null")
378+
{
379+
return Expression.Constant(null, typeof(object));
380+
}
381+
382+
if (bool.TryParse(right, out var boolVal))
383+
{
384+
return Expression.Constant(boolVal, typeof(bool));
385+
}
386+
}
361387

362388
throw new InvalidOperationException($"Unsupported value '{right}' for type '{targetType.Name}'");
363389
}
@@ -499,10 +525,17 @@ private static Parser<Expression> ComparisonExprParser<T>(ParameterExpression pa
499525
}
500526
catch(ArgumentException)
501527
{
528+
var derivedPropertyInfo = config?.PropertyMappings?.GetDerivedPropertyInfoByQueryName(fullPropPath);
529+
if (derivedPropertyInfo?.DerivedExpression != null)
530+
{
531+
return derivedPropertyInfo.DerivedExpression;
532+
}
533+
502534
if(config?.AllowUnknownProperties == true)
503535
{
504536
return Expression.Constant(true, typeof(bool));
505537
}
538+
506539
throw new UnknownFilterPropertyException(actualPropertyName);
507540
}
508541
});
@@ -516,7 +549,7 @@ private static Parser<Expression> ComparisonExprParser<T>(ParameterExpression pa
516549
return propertyExpression;
517550
});
518551
}
519-
552+
520553
private static Type? GetInnerGenericType(Type type)
521554
{
522555
if (!IsEnumerable(type))
@@ -549,3 +582,5 @@ private static Parser<Expression> OrExprParser<T>(ParameterExpression parameter,
549582
(op, left, right) => op.GetExpression<T>(left, right)
550583
);
551584
}
585+
586+

‎QueryKit/Operators/ComparisonOperator.cs

+12
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,12 @@ public override Expression GetExpression<T>(Expression left, Expression right, T
205205
Expression.Call(right, typeof(string).GetMethod("ToLower", Type.EmptyTypes))
206206
);
207207
}
208+
209+
// for some complex derived expressions
210+
if (left.NodeType == ExpressionType.Convert)
211+
{
212+
left = Expression.Convert(left, typeof(bool));
213+
}
208214

209215
return Expression.Equal(left, right);
210216
}
@@ -231,6 +237,12 @@ public override Expression GetExpression<T>(Expression left, Expression right, T
231237
Expression.Call(right, typeof(string).GetMethod("ToLower", Type.EmptyTypes))
232238
);
233239
}
240+
241+
// for some complex derived expressions
242+
if (left.NodeType == ExpressionType.Convert)
243+
{
244+
left = Expression.Convert(left, typeof(bool));
245+
}
234246

235247
return Expression.NotEqual(left, right);
236248
}

‎QueryKit/ParameterReplacer.cs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace QueryKit;
2+
3+
using System.Linq.Expressions;
4+
5+
internal class ParameterReplacer : ExpressionVisitor
6+
{
7+
private readonly ParameterExpression _newParameter;
8+
9+
public ParameterReplacer(ParameterExpression newParameter)
10+
{
11+
_newParameter = newParameter;
12+
}
13+
14+
protected override Expression VisitParameter(ParameterExpression node)
15+
{
16+
// Replace all parameters with the new parameter
17+
return _newParameter;
18+
}
19+
}

‎QueryKit/QueryKitPropertyMappings.cs

+118-30
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ namespace QueryKit;
88
public class QueryKitPropertyMappings
99
{
1010
private readonly Dictionary<string, QueryKitPropertyInfo> _propertyMappings = new();
11+
private readonly Dictionary<string, QueryKitPropertyInfo> _derivedPropertyMappings = new();
1112
internal IReadOnlyDictionary<string, QueryKitPropertyInfo> PropertyMappings => _propertyMappings;
13+
internal IReadOnlyDictionary<string, QueryKitPropertyInfo> DerivedPropertyMappings => _derivedPropertyMappings;
1214

1315
public QueryKitPropertyMapping<TModel> Property<TModel>(Expression<Func<TModel, object>>? propertySelector)
1416
{
@@ -25,7 +27,32 @@ public QueryKitPropertyMapping<TModel> Property<TModel>(Expression<Func<TModel,
2527

2628
return new QueryKitPropertyMapping<TModel>(propertyInfo);
2729
}
28-
30+
31+
public QueryKitPropertyMapping<TModel> DerivedProperty<TModel>(Expression<Func<TModel, object>>? propertySelector)
32+
{
33+
var fullPath = GetFullPropertyPath(propertySelector);
34+
35+
if (propertySelector == null)
36+
throw new ArgumentNullException(nameof(propertySelector));
37+
if(propertySelector.NodeType != ExpressionType.Lambda)
38+
throw new ArgumentException("Property selector must be a lambda expression", nameof(propertySelector));
39+
40+
var body = propertySelector.Body;
41+
42+
var propertyInfo = new QueryKitPropertyInfo
43+
{
44+
Name = fullPath,
45+
CanFilter = true,
46+
CanSort = true,
47+
QueryName = fullPath,
48+
DerivedExpression = body
49+
};
50+
51+
_derivedPropertyMappings[fullPath] = propertyInfo;
52+
53+
return new QueryKitPropertyMapping<TModel>(propertyInfo);
54+
}
55+
2956
public string ReplaceAliasesWithPropertyPaths(string input)
3057
{
3158
var operators = ComparisonOperator.List.Select(x => x.Operator()).ToList();
@@ -42,56 +69,116 @@ public string ReplaceAliasesWithPropertyPaths(string input)
4269
}
4370
}
4471
}
45-
72+
73+
// foreach (var alias in _derivedPropertyMappings.Values)
74+
// {
75+
// foreach (var op in operators)
76+
// {
77+
// // Use regular expression to isolate left side of the expression
78+
// var regex = new Regex($@"\b{alias.QueryName}\b(?=\s*{op})", RegexOptions.IgnoreCase);
79+
// input = regex.Replace(input, $"~||~||~{alias.QueryName}");
80+
// }
81+
// }
4682
return input;
4783
}
4884

4985
private static string GetFullPropertyPath(Expression? expression)
5086
{
51-
if (expression!.NodeType == ExpressionType.Call)
52-
{
53-
var call = (MethodCallExpression)expression;
54-
if (call.Method.DeclaringType == typeof(Enumerable) && call.Method.Name == "Select" ||
55-
call.Method.DeclaringType == typeof(Queryable) && call.Method.Name == "Select" ||
56-
call.Method.DeclaringType == typeof(Enumerable) && call.Method.Name == "SelectMany" ||
57-
call.Method.DeclaringType == typeof(Queryable) && call.Method.Name == "SelectMany")
58-
{
59-
var propertyPath = GetFullPropertyPath(call.Arguments[1]);
60-
var prevPath = GetFullPropertyPath(call.Arguments[0]);
61-
return $"{prevPath}.{propertyPath}";
62-
}
63-
}
87+
if (expression == null)
88+
throw new ArgumentNullException(nameof(expression));
6489

65-
if (expression!.NodeType == ExpressionType.Lambda)
66-
{
67-
var lambda = (LambdaExpression)expression;
68-
return GetFullPropertyPath(lambda.Body);
69-
}
70-
if (expression.NodeType == ExpressionType.Convert)
71-
{
72-
var unary = (UnaryExpression)expression;
73-
return GetFullPropertyPath(unary.Operand);
74-
}
75-
if (expression.NodeType == ExpressionType.MemberAccess)
90+
switch (expression.NodeType)
7691
{
77-
var memberExpression = (MemberExpression)expression;
78-
return memberExpression?.Expression?.NodeType == ExpressionType.Parameter
79-
? memberExpression.Member.Name
80-
: $"{GetFullPropertyPath(memberExpression?.Expression)}.{memberExpression?.Member?.Name}";
92+
case ExpressionType.Call:
93+
var call = (MethodCallExpression)expression;
94+
if (call.Method.DeclaringType == typeof(Enumerable) && call.Method.Name == "Select" ||
95+
call.Method.DeclaringType == typeof(Queryable) && call.Method.Name == "Select" ||
96+
call.Method.DeclaringType == typeof(Enumerable) && call.Method.Name == "SelectMany" ||
97+
call.Method.DeclaringType == typeof(Queryable) && call.Method.Name == "SelectMany")
98+
{
99+
var propertyPath = GetFullPropertyPath(call.Arguments[1]);
100+
var prevPath = GetFullPropertyPath(call.Arguments[0]);
101+
return $"{prevPath}.{propertyPath}";
102+
}
103+
break;
104+
case ExpressionType.Lambda:
105+
var lambda = (LambdaExpression)expression;
106+
return GetFullPropertyPath(lambda.Body);
107+
case ExpressionType.Convert:
108+
var unary = (UnaryExpression)expression;
109+
return GetFullPropertyPath(unary.Operand);
110+
case ExpressionType.MemberAccess:
111+
var memberExpression = (MemberExpression)expression;
112+
return memberExpression?.Expression?.NodeType == ExpressionType.Parameter
113+
? memberExpression.Member.Name
114+
: $"{GetFullPropertyPath(memberExpression?.Expression)}.{memberExpression?.Member?.Name}";
115+
case ExpressionType.Add:
116+
case ExpressionType.Subtract:
117+
case ExpressionType.Multiply:
118+
case ExpressionType.Divide:
119+
case ExpressionType.Modulo:
120+
case ExpressionType.And:
121+
case ExpressionType.Or:
122+
case ExpressionType.AndAlso:
123+
case ExpressionType.OrElse:
124+
case ExpressionType.GreaterThan:
125+
case ExpressionType.LessThan:
126+
case ExpressionType.GreaterThanOrEqual:
127+
case ExpressionType.LessThanOrEqual:
128+
case ExpressionType.Equal:
129+
var binary = (BinaryExpression)expression;
130+
var left = GetFullPropertyPath(binary.Left);
131+
var right = GetFullPropertyPath(binary.Right);
132+
var op = GetOperator(binary.NodeType);
133+
return $"{left} {op} {right}";
134+
case ExpressionType.Constant:
135+
var constant = (ConstantExpression)expression;
136+
return constant.Value?.ToString() ?? "";
137+
default:
138+
throw new NotSupportedException($"Expression type '{expression.NodeType}' is not supported.");
81139
}
140+
82141
throw new NotSupportedException($"Expression type '{expression.NodeType}' is not supported.");
83142
}
84143

144+
private static string GetOperator(ExpressionType nodeType)
145+
{
146+
return nodeType switch
147+
{
148+
ExpressionType.Add => "+",
149+
ExpressionType.Subtract => "-",
150+
ExpressionType.Multiply => "*",
151+
ExpressionType.Divide => "/",
152+
ExpressionType.Modulo => "%",
153+
ExpressionType.And => "&",
154+
ExpressionType.Or => "|",
155+
ExpressionType.AndAlso => "&&",
156+
ExpressionType.OrElse => "||",
157+
ExpressionType.GreaterThan => ">",
158+
ExpressionType.LessThan => "<",
159+
ExpressionType.GreaterThanOrEqual => ">=",
160+
ExpressionType.LessThanOrEqual => "<=",
161+
ExpressionType.Equal => "==",
162+
_ => throw new NotSupportedException($"Operator for expression type '{nodeType}' is not supported.")
163+
};
164+
}
165+
166+
167+
85168
public QueryKitPropertyInfo? GetPropertyInfo(string? propertyName)
86169
=> _propertyMappings.TryGetValue(propertyName, out var info) ? info : null;
87170

88171
public QueryKitPropertyInfo? GetPropertyInfoByQueryName(string? queryName)
89172
=> _propertyMappings.Values.FirstOrDefault(info => info.QueryName != null && info.QueryName.Equals(queryName, StringComparison.InvariantCultureIgnoreCase));
90173

174+
public QueryKitPropertyInfo? GetDerivedPropertyInfoByQueryName(string? queryName)
175+
=> _derivedPropertyMappings.Values.FirstOrDefault(info => info.QueryName != null && info.QueryName.Equals(queryName, StringComparison.InvariantCultureIgnoreCase));
176+
91177
public string? GetPropertyPathByQueryName(string? queryName)
92178
=> GetPropertyInfoByQueryName(queryName)?.Name ?? null;
93179
}
94180

181+
95182
public class QueryKitPropertyMapping<TModel>
96183
{
97184
private readonly QueryKitPropertyInfo _propertyInfo;
@@ -126,4 +213,5 @@ public class QueryKitPropertyInfo
126213
public bool CanFilter { get; set; }
127214
public bool CanSort { get; set; }
128215
public string? QueryName { get; set; }
216+
internal Expression DerivedExpression { get; set; }
129217
}

‎README.md

+16
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,22 @@ var config = new QueryKitConfiguration(config =>
223223
});
224224
```
225225

226+
#### Derived Properties
227+
228+
You can also expose custom derived properties for consumption. Just be sure that Linq can handle them in a db query if you're using it that way.
229+
230+
```csharp
231+
var config = new QueryKitConfiguration(config =>
232+
{
233+
config.DerivedProperty<Person>(p => p.FirstName + " " + p.LastName).HasQueryName("fullname");
234+
config.DerivedProperty<Person>(p => p.Age >= 18 && p.FirstName == "John").HasQueryName("adult_johns");
235+
});
236+
237+
var input = $"""(fullname @=* "John Doe") && age >= 18""";
238+
// or
239+
var input = $"""adult_johns == true""";
240+
```
241+
226242
#### Custom Operators
227243

228244
You can also add custom comparison operators to your config if you'd like:

‎SharedTestingHelper/Fakes/FakeTestingPersonBuilder.cs

+20
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace SharedTestingHelper.Fakes;
66
public class FakeTestingPersonBuilder
77
{
88
private readonly TestingPerson _baseTestingPerson = new AutoFaker<TestingPerson>()
9+
.RuleFor(x => x.FirstName, faker => faker.Name.FirstName())
10+
.RuleFor(x => x.LastName, faker => faker.Name.LastName())
911
.RuleFor(x => x.Title, faker => faker.Lorem.Sentence())
1012
.Generate();
1113

@@ -21,6 +23,12 @@ public FakeTestingPersonBuilder WithAge(int age)
2123
return this;
2224
}
2325

26+
public FakeTestingPersonBuilder WithFavorite(bool favorite)
27+
{
28+
_baseTestingPerson.Favorite = favorite;
29+
return this;
30+
}
31+
2432
public FakeTestingPersonBuilder WithEmail(string email)
2533
{
2634
_baseTestingPerson.Email = new EmailAddress(email);
@@ -75,5 +83,17 @@ public FakeTestingPersonBuilder WithTime(TimeOnly? time)
7583
return this;
7684
}
7785

86+
public FakeTestingPersonBuilder WithFirstName(string firstName)
87+
{
88+
_baseTestingPerson.FirstName = firstName;
89+
return this;
90+
}
91+
92+
public FakeTestingPersonBuilder WithLastName(string lastName)
93+
{
94+
_baseTestingPerson.LastName = lastName;
95+
return this;
96+
}
97+
7898
public TestingPerson Build() => _baseTestingPerson;
7999
}

0 commit comments

Comments
 (0)
Please sign in to comment.