Skip to content

Commit

Permalink
feat: support nested property
Browse files Browse the repository at this point in the history
  • Loading branch information
saeed committed Jun 4, 2024
1 parent a47ffac commit 91624e1
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 10 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Welcome to the **SpelCompiler**! This library aims to provide .NET developers wi

- Compile 'where' expressions from SpEL to .NET
- Execute compiled expressions in a .NET environment

- Support nested property
Usage
Here’s a basic example of how to use the library:

Expand Down
4 changes: 3 additions & 1 deletion src/SpelParser/SpelGrammer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ comparision
| Field=field Operator='!=' Constant=constant #NotEqualExpression
;

field : FIELD;
field : FIELD'.'FIELD # NestedPropertyExpression
| FIELD # FieldExpression
;

constant
: STRING #String
Expand Down
28 changes: 24 additions & 4 deletions src/SpelParser/SpelGrammerCompiler.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Antlr4.Runtime;
using Antlr4.Runtime.Misc;
using Antlr4.Runtime.Tree;
using SpelParser.Generated;
using System;
using System.Linq.Expressions;
Expand All @@ -15,6 +16,15 @@ public class SpelGrammerCompiler<T> : SpelGrammerBaseVisitor<Expression>

private static string GetString(ConstantContext context) => context.GetText().Trim('"', '\'');

private static Expression CreateFieldExpression(ITerminalNode token)
{
var fieldNameToken = token.GetText();
var fieldExpression = typeof(T).GetProperty(fieldNameToken, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)!;

ArgumentNullException.ThrowIfNull(fieldExpression, fieldNameToken);
return Expression.Property(_param, fieldExpression.Name);
}

public override Expression VisitString([NotNull] StringContext context)
{
return Expression.Constant(GetString(context), typeof(string));
Expand All @@ -32,11 +42,21 @@ public override Expression VisitConstant([NotNull] ConstantContext context)

public override Expression VisitField([NotNull] FieldContext context)
{
var fieldNameToken = context.FIELD().GetText();
var fieldExpression = typeof(T).GetProperty(fieldNameToken, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)!;
return base.Visit(context);
}
public override Expression VisitErrorNode([NotNull] IErrorNode node)
{
node.GetText();
return base.VisitErrorNode(node);
}
public override Expression VisitNestedPropertyExpression([NotNull] NestedPropertyExpressionContext context)
{
return Expression.PropertyOrField(CreateFieldExpression(context.FIELD(0)), context.FIELD(1).GetText());
}

ArgumentNullException.ThrowIfNull(fieldExpression, fieldNameToken);
return Expression.Property(_param, fieldExpression.Name);
public override Expression VisitFieldExpression([NotNull] FieldExpressionContext context)
{
return CreateFieldExpression(context.FIELD());
}

public override Expression VisitAnd([NotNull] AndContext context)
Expand Down
4 changes: 2 additions & 2 deletions src/SpelParser/SpelParser.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<PackageVersion>1.0.201</PackageVersion>
<PackageVersion>1.1.0</PackageVersion>
<PackageId>SpelParser</PackageId>
<Version>1.0.201</Version>
<Version>1.1.0</Version>
<Authors>Saeed Bolhasani</Authors>
<PackageDescription>Convert string to expression</PackageDescription>
<RepositoryUrl>https://github.com/SaeedBolhasani/SpelParser</RepositoryUrl>
Expand Down
36 changes: 34 additions & 2 deletions test/SpelParser.UnitTests/SpelGrammerCompilerUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ public enum EmployeeType : byte
Employee = 2
}

public class Address
{
public string City { get;set; }

Check warning on line 13 in test/SpelParser.UnitTests/SpelGrammerCompilerUnitTests.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'City' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 13 in test/SpelParser.UnitTests/SpelGrammerCompilerUnitTests.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'City' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public string PostalCode { get;set; }

Check warning on line 14 in test/SpelParser.UnitTests/SpelGrammerCompilerUnitTests.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'PostalCode' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 14 in test/SpelParser.UnitTests/SpelGrammerCompilerUnitTests.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'PostalCode' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
}
public class EmployeeModel
{
public int Age { get; set; }
Expand All @@ -18,6 +23,7 @@ public class EmployeeModel
public TimeSpan WorkingDuration { get; set; }
public decimal AccountBalance { get; set; }
public EmployeeType EmployeeType { get; set; }
public Address Address { get; set; }

Check warning on line 26 in test/SpelParser.UnitTests/SpelGrammerCompilerUnitTests.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Address' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 26 in test/SpelParser.UnitTests/SpelGrammerCompilerUnitTests.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Address' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
}

public class SpelGrammerCompilerUnitTests
Expand Down Expand Up @@ -251,6 +257,22 @@ public void CreateFunc_QueryWithEnum_ResultSetShouldBeContainOnlyEligibleItems()
result.Should().HaveCount(1);
}

[Fact]
public void CreateFunc_QueryWithNestedProperty_ResultSetShouldBeContainOnlyEligibleItems()
{
var postalCode = "1";

var compiler = new SpelGrammerCompiler<EmployeeModel>();

var input = $"address.postalCode == '{postalCode}' ";
var query = compiler.CreateFunc(input);

var result = _models.Where(query.Compile()).ToArray();

result.Should().AllSatisfy(i => i.Address.PostalCode.Should().Be(postalCode));
result.Should().HaveCount(1);
}

private readonly EmployeeModel[] _models =
[
new EmployeeModel
Expand All @@ -262,7 +284,12 @@ public void CreateFunc_QueryWithEnum_ResultSetShouldBeContainOnlyEligibleItems()
Name = "Ali",
BedTime = TimeOnly.Parse("19:00"),
WorkingDuration = TimeSpan.FromHours(8),
EmployeeType = EmployeeType.Employee
EmployeeType = EmployeeType.Employee,
Address = new Address
{
City = "Tehran",
PostalCode = "1"
}
},
new EmployeeModel
{
Expand All @@ -273,7 +300,12 @@ public void CreateFunc_QueryWithEnum_ResultSetShouldBeContainOnlyEligibleItems()
Name = "Saeed",
BedTime = TimeOnly.Parse("21:00"),
WorkingDuration = TimeSpan.FromHours(8),
EmployeeType = EmployeeType.Manager
EmployeeType = EmployeeType.Manager,
Address = new Address
{
City = "New York",
PostalCode = "2"
}
}
];

Expand Down

0 comments on commit 91624e1

Please sign in to comment.