diff --git a/Ctoss.Example/Ctoss.Example.csproj b/Ctoss.Example/Ctoss.Example.csproj
new file mode 100644
index 0000000..a17122b
--- /dev/null
+++ b/Ctoss.Example/Ctoss.Example.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+ Exe
+
+
+
+
+
+
+
diff --git a/Ctoss.Example/Program.cs b/Ctoss.Example/Program.cs
new file mode 100644
index 0000000..f3c2619
--- /dev/null
+++ b/Ctoss.Example/Program.cs
@@ -0,0 +1,36 @@
+using Ctoss;
+using Ctoss.Example;
+
+const string jsonString = """
+ {
+ "tin": {
+ "filterType": "date",
+ "condition1": {
+ "filterType": "date",
+ "type": "inRange",
+ "dateFrom": "10/10/2002",
+ "dateTo": "10/12/2020"
+ },
+ "conditions": [
+ {
+ "filterType": "date",
+ "type": "inRange",
+ "date": "10/10/2002",
+ "dateTo": "10/12/2020"
+ }
+ ]
+ }
+ }
+ """;
+
+var filterBuilder = new FilterBuilder();
+var expr = filterBuilder.GetExpression(jsonString);
+Console.WriteLine(expr);
+
+namespace Ctoss.Example
+{
+ class Entity
+ {
+ public DateTime Tin { get; set; }
+ }
+}
diff --git a/Ctoss.Tests/Ctoss.Tests.csproj b/Ctoss.Tests/Ctoss.Tests.csproj
new file mode 100644
index 0000000..9cd9fab
--- /dev/null
+++ b/Ctoss.Tests/Ctoss.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Ctoss.Tests/DateFilterTests.cs b/Ctoss.Tests/DateFilterTests.cs
new file mode 100644
index 0000000..43b0548
--- /dev/null
+++ b/Ctoss.Tests/DateFilterTests.cs
@@ -0,0 +1,235 @@
+using Ctoss.Models;
+using Ctoss.Models.Conditions;
+using Ctoss.Models.Enums;
+using Ctoss.Tests.Models;
+
+namespace Ctoss.Tests;
+
+public class DateFilterTests
+{
+ private readonly FilterBuilder _filterBuilder = new();
+
+ private readonly List _testEntities =
+ [
+ new TestEntity
+ {
+ NumericProperty = 10, StringProperty = "abc", DateTimeProperty = new DateTime(2022, 1, 1)
+ },
+ new TestEntity
+ {
+ NumericProperty = 20, StringProperty = "def", DateTimeProperty = new DateTime(2023, 2, 2)
+ },
+ new TestEntity
+ {
+ NumericProperty = 30, StringProperty = "ghi", DateTimeProperty = new DateTime(2024, 3, 3)
+ }
+ ];
+
+ [Fact]
+ public void DateFilter_Equals_Success()
+ {
+ var condition = new DateFilterCondition
+ {
+ DateFrom = "01/01/2022",
+ FilterType = "date",
+ Type = DateFilterOptions.Equals
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "date",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("DateTimeProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(new DateTime(2022, 1, 1), result.First().DateTimeProperty);
+ }
+
+ [Fact]
+ public void DateFilter_GreaterThen_Success()
+ {
+ var condition = new DateFilterCondition
+ {
+ DateFrom = "02/02/2023",
+ FilterType = "date",
+ Type = DateFilterOptions.GreaterThen
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "date",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("DateTimeProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(new DateTime(2024, 3, 3), result.First().DateTimeProperty);
+ }
+
+ [Fact]
+ public void DateFilter_Blank_Success()
+ {
+ var condition = new DateFilterCondition
+ {
+ FilterType = "date",
+ Type = DateFilterOptions.Blank
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "date",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("DateTimeProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Empty(result);
+ }
+
+ [Fact]
+ public void DateFilter_LessThen_Success()
+ {
+ var condition = new DateFilterCondition
+ {
+ DateFrom = "01/01/2023",
+ FilterType = "date",
+ Type = DateFilterOptions.LessThen
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "date",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("DateTimeProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(new DateTime(2022, 1, 1), result.First().DateTimeProperty);
+ }
+
+ [Fact]
+ public void DateFilter_NotBlank_Success()
+ {
+ var condition = new DateFilterCondition
+ {
+ FilterType = "date",
+ Type = DateFilterOptions.NotBlank
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "date",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("DateTimeProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Equal(3, result.Count);
+ }
+
+ [Fact]
+ public void DateFilter_InRange_Success()
+ {
+ var condition = new DateFilterCondition
+ {
+ DateFrom = "06/06/2021",
+ DateTo = "09/09/2024",
+ FilterType = "date",
+ Type = DateFilterOptions.InRange
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "date",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("DateTimeProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Equal(3, result.Count);
+ }
+
+ [Fact]
+ public void DateFilter_NotEquals_Success()
+ {
+ var condition1 = new DateFilterCondition
+ {
+ DateFrom = "01/01/2022",
+ FilterType = "date",
+ Type = DateFilterOptions.NotEquals
+ };
+ var condition2 = new DateFilterCondition
+ {
+ DateFrom = "03/03/2024",
+ FilterType = "date",
+ Type = DateFilterOptions.NotEquals
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "date",
+ Operator = Operator.And,
+ Condition1 = condition1,
+ Condition2 = condition2,
+ Conditions = new List { condition1, condition2 }
+ };
+
+ var expr = _filterBuilder.GetExpression("DateTimeProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(new DateTime(2023, 02, 02), result.First().DateTimeProperty);
+ }
+
+ [Fact]
+ public void DateFilter_Composed_Success()
+ {
+ var condition1 = new DateFilterCondition
+ {
+ DateFrom = "01/01/2022",
+ FilterType = "date",
+ Type = DateFilterOptions.NotEquals
+ };
+
+ var condition2 = new DateFilterCondition
+ {
+ DateFrom = "03/03/2024",
+ FilterType = "date",
+ Type = DateFilterOptions.LessThen
+ };
+
+ var filter = new NumberFilter
+ {
+ Operator = Operator.And,
+ FilterType = "date",
+ Condition1 = condition1,
+ Condition2 = condition2,
+ Conditions = new List
+ {
+ condition1, condition2
+ }
+ };
+
+ var expr = _filterBuilder.GetExpression("DateTimeProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(new DateTime(2023, 02, 02), result.First().DateTimeProperty);
+ }
+}
diff --git a/Ctoss.Tests/Models/TestEntity.cs b/Ctoss.Tests/Models/TestEntity.cs
new file mode 100644
index 0000000..9cd7b91
--- /dev/null
+++ b/Ctoss.Tests/Models/TestEntity.cs
@@ -0,0 +1,8 @@
+namespace Ctoss.Tests.Models;
+
+public class TestEntity
+{
+ public string StringProperty { get; set; } = null!;
+ public DateTime DateTimeProperty { get; set; }
+ public int NumericProperty { get; set; }
+}
diff --git a/Ctoss.Tests/NumberFilterTests.cs b/Ctoss.Tests/NumberFilterTests.cs
new file mode 100644
index 0000000..8765150
--- /dev/null
+++ b/Ctoss.Tests/NumberFilterTests.cs
@@ -0,0 +1,240 @@
+using Ctoss.Models;
+using Ctoss.Models.Conditions;
+using Ctoss.Models.Enums;
+using Ctoss.Tests.Models;
+
+namespace Ctoss.Tests;
+
+public class NumberFilterTests
+{
+ private readonly FilterBuilder _filterBuilder = new();
+
+ private readonly List _testEntities =
+ [
+ new TestEntity
+ {
+ NumericProperty = 10, StringProperty = "abc", DateTimeProperty = new DateTime(2022, 1, 1)
+ },
+ new TestEntity
+ {
+ NumericProperty = 20, StringProperty = "def", DateTimeProperty = new DateTime(2023, 2, 2)
+ },
+ new TestEntity
+ {
+ NumericProperty = 30, StringProperty = "ghi", DateTimeProperty = new DateTime(2024, 3, 3)
+ }
+ ];
+
+ [Fact]
+ public void NumericFilter_Equals_Success()
+ {
+ var condition = new NumberFilterCondition
+ {
+ Filter = 10,
+ FilterType = "number",
+ Type = NumberFilterOptions.Equals
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "number",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("NumericProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(10, result.First().NumericProperty);
+ }
+
+ [Fact]
+ public void NumericFilter_GreaterThen_Success()
+ {
+ var condition = new NumberFilterCondition
+ {
+ Filter = 20,
+ FilterType = "number",
+ Type = NumberFilterOptions.GreaterThan
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "number",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("NumericProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(30, result.First().NumericProperty);
+ }
+
+ [Fact]
+ public void NumericFilter_GreaterThenOrEquals_Success()
+ {
+ var condition = new NumberFilterCondition
+ {
+ Filter = 30,
+ FilterType = "number",
+ Type = NumberFilterOptions.GreaterThanOrEqual
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "number",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("NumericProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(30, result.First().NumericProperty);
+ }
+
+ [Fact]
+ public void NumericFilter_LessThen_Success()
+ {
+ var condition = new NumberFilterCondition
+ {
+ Filter = 20,
+ FilterType = "number",
+ Type = NumberFilterOptions.LessThan
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "number",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("NumericProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(10, result.First().NumericProperty);
+ }
+
+ [Fact]
+ public void NumericFilter_LessThenOrEquals_Success()
+ {
+ var condition = new NumberFilterCondition
+ {
+ Filter = 10,
+ FilterType = "number",
+ Type = NumberFilterOptions.LessThanOrEqual
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "number",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("NumericProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(10, result.First().NumericProperty);
+ }
+
+ [Fact]
+ public void NumericFilter_InRange_Success()
+ {
+ var condition = new NumberFilterCondition
+ {
+ Filter = 0,
+ FilterTo = 12,
+ FilterType = "number",
+ Type = NumberFilterOptions.InRange
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "number",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("NumericProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(10, result.First().NumericProperty);
+ }
+
+ [Fact]
+ public void NumericFilter_NotEquals_Success()
+ {
+ var condition1 = new NumberFilterCondition
+ {
+ Filter = 10,
+ FilterType = "number",
+ Type = NumberFilterOptions.NotEquals
+ };
+ var condition2 = new NumberFilterCondition
+ {
+ Filter = 20,
+ FilterType = "number",
+ Type = NumberFilterOptions.NotEquals
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "number",
+ Operator = Operator.And,
+ Condition1 = condition1,
+ Condition2 = condition2,
+ Conditions = new List { condition1, condition2 }
+ };
+
+ var expr = _filterBuilder.GetExpression("NumericProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(30, result.First().NumericProperty);
+ }
+
+ [Fact]
+ public void NumericFilter_Composed_Success()
+ {
+ var condition1 = new NumberFilterCondition
+ {
+ Filter = 25,
+ FilterType = "number",
+ Type = NumberFilterOptions.LessThan
+ };
+
+ var condition2 = new NumberFilterCondition
+ {
+ Filter = 10,
+ FilterType = "number",
+ Type = NumberFilterOptions.NotEquals
+ };
+
+ var filter = new NumberFilter
+ {
+ Operator = Operator.And,
+ FilterType = "number",
+ Condition1 = condition1,
+ Condition2 = condition2,
+ Conditions = new List
+ {
+ condition1, condition2
+ }
+ };
+
+ var expr = _filterBuilder.GetExpression("NumericProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(20, result.First().NumericProperty);
+ }
+}
diff --git a/Ctoss.Tests/TextFilterTests.cs b/Ctoss.Tests/TextFilterTests.cs
new file mode 100644
index 0000000..135f31b
--- /dev/null
+++ b/Ctoss.Tests/TextFilterTests.cs
@@ -0,0 +1,213 @@
+using Ctoss.Models;
+using Ctoss.Models.Conditions;
+using Ctoss.Models.Enums;
+using Ctoss.Tests.Models;
+
+namespace Ctoss.Tests;
+
+public class TextFilterTests
+{
+ private readonly FilterBuilder _filterBuilder = new();
+
+ private readonly List _testEntities =
+ [
+ new TestEntity
+ {
+ NumericProperty = 10, StringProperty = "abc", DateTimeProperty = new DateTime(2022, 1, 1)
+ },
+ new TestEntity
+ {
+ NumericProperty = 20, StringProperty = "def", DateTimeProperty = new DateTime(2023, 2, 2)
+ },
+ new TestEntity
+ {
+ NumericProperty = 30, StringProperty = "ghi", DateTimeProperty = new DateTime(2024, 3, 3)
+ }
+ ];
+
+ [Fact]
+ public void TextFilter_Equals_Success()
+ {
+ var condition = new TextFilterCondition
+ {
+ Filter = "abc",
+ FilterType = "text",
+ Type = TextFilterOptions.Equals
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "text",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("StringProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal("abc", result.First().StringProperty);
+ }
+
+ [Fact]
+ public void TextFilter_StartsWith_Success()
+ {
+ var condition = new TextFilterCondition
+ {
+ Filter = "a",
+ FilterType = "text",
+ Type = TextFilterOptions.StartsWith
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "text",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("StringProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal("abc", result.First().StringProperty);
+ }
+
+ [Fact]
+ public void TextFilter_EndsWith_Success()
+ {
+ var condition = new TextFilterCondition
+ {
+ Filter = "c",
+ FilterType = "text",
+ Type = TextFilterOptions.EndsWith
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "text",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("StringProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal("abc", result.First().StringProperty);
+ }
+
+ [Fact]
+ public void TextFilter_NotBlank_Success()
+ {
+ var condition = new TextFilterCondition
+ {
+ FilterType = "text",
+ Type = TextFilterOptions.NotBlank
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "text",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("StringProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Equal("abc", result.First().StringProperty);
+ }
+
+ [Fact]
+ public void TextFilter__Success()
+ {
+ var condition = new TextFilterCondition
+ {
+ Filter = "ab",
+ FilterType = "text",
+ Type = TextFilterOptions.Contains
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "text",
+ Condition1 = condition,
+ Conditions = new List { condition }
+ };
+
+ var expr = _filterBuilder.GetExpression("StringProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal("abc", result.First().StringProperty);
+ }
+
+ [Fact]
+ public void TextFilter_NotEquals_Success()
+ {
+ var condition1 = new TextFilterCondition
+ {
+ Filter = "abc",
+ FilterType = "text",
+ Type = TextFilterOptions.NotEquals
+ };
+ var condition2 = new TextFilterCondition
+ {
+ Filter = "ghi",
+ FilterType = "text",
+ Type = TextFilterOptions.NotEquals
+ };
+
+ var filter = new NumberFilter
+ {
+ FilterType = "text",
+ Operator = Operator.And,
+ Condition1 = condition1,
+ Condition2 = condition2,
+ Conditions = new List { condition1, condition2 }
+ };
+
+ var expr = _filterBuilder.GetExpression("StringProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal("def", result.First().StringProperty);
+ }
+
+ [Fact]
+ public void TextFilter_Composed_Success()
+ {
+ var condition1 = new TextFilterCondition
+ {
+ Filter = "def",
+ FilterType = "text",
+ Type = TextFilterOptions.NotEquals
+ };
+
+ var condition2 = new TextFilterCondition
+ {
+ Filter = "a",
+ FilterType = "text",
+ Type = TextFilterOptions.StartsWith
+ };
+
+ var filter = new NumberFilter
+ {
+ Operator = Operator.And,
+ FilterType = "text",
+ Condition1 = condition1,
+ Condition2 = condition2,
+ Conditions = new List
+ {
+ condition1, condition2
+ }
+ };
+
+ var expr = _filterBuilder.GetExpression("StringProperty", filter)!;
+ var result = _testEntities.AsQueryable().Where(expr).ToList();
+
+ Assert.Single(result);
+ Assert.Equal("abc", result.First().StringProperty);
+ }
+}
diff --git a/Ctoss.sln b/Ctoss.sln
new file mode 100644
index 0000000..da16eef
--- /dev/null
+++ b/Ctoss.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ctoss", "Ctoss\Ctoss.csproj", "{F241585F-D3A7-4573-9D21-CA3526270F05}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ctoss.Example", "Ctoss.Example\Ctoss.Example.csproj", "{BF1FB2EA-7493-4CB0-9790-EDB4487D0353}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ctoss.Tests", "Ctoss.Tests\Ctoss.Tests.csproj", "{E4AB8A1B-2057-499E-BB12-28D61A5815BE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F241585F-D3A7-4573-9D21-CA3526270F05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F241585F-D3A7-4573-9D21-CA3526270F05}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F241585F-D3A7-4573-9D21-CA3526270F05}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F241585F-D3A7-4573-9D21-CA3526270F05}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BF1FB2EA-7493-4CB0-9790-EDB4487D0353}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BF1FB2EA-7493-4CB0-9790-EDB4487D0353}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BF1FB2EA-7493-4CB0-9790-EDB4487D0353}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BF1FB2EA-7493-4CB0-9790-EDB4487D0353}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E4AB8A1B-2057-499E-BB12-28D61A5815BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E4AB8A1B-2057-499E-BB12-28D61A5815BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E4AB8A1B-2057-499E-BB12-28D61A5815BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E4AB8A1B-2057-499E-BB12-28D61A5815BE}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/Ctoss/Ctoss.csproj b/Ctoss/Ctoss.csproj
new file mode 100644
index 0000000..5622bbb
--- /dev/null
+++ b/Ctoss/Ctoss.csproj
@@ -0,0 +1,10 @@
+
+
+
+ net8.0
+ enable
+ enable
+ Ctoss
+
+
+
diff --git a/Ctoss/Extensions/ExpressionExtensions.cs b/Ctoss/Extensions/ExpressionExtensions.cs
new file mode 100644
index 0000000..6cd5994
--- /dev/null
+++ b/Ctoss/Extensions/ExpressionExtensions.cs
@@ -0,0 +1,59 @@
+using System.Linq.Expressions;
+
+namespace Ctoss.Extensions;
+
+///
+/// Provides extension methods for combining expressions.
+///
+public static class ExpressionExtensions
+{
+ ///
+ /// Combines two expressions using a logical AND operation.
+ ///
+ /// The type of the entity.
+ /// The left expression.
+ /// The right expression.
+ /// An expression that represents the logical AND of the input expressions.
+ public static Expression> AndAlso(
+ this Expression> left,
+ Expression> right)
+ {
+ return left.Compose(right, Expression.AndAlso);
+ }
+
+ ///
+ /// Combines two expressions using a logical OR operation.
+ ///
+ /// The type of the entity.
+ /// The left expression.
+ /// The right expression.
+ /// An expression that represents the logical OR of the input expressions.
+ public static Expression> OrElse(
+ this Expression> left,
+ Expression> right)
+ {
+ return left.Compose(right, Expression.OrElse);
+ }
+
+ ///
+ /// Composes two expressions using a specified merge function.
+ ///
+ /// The type of the delegate.
+ /// The left expression.
+ /// The right expression.
+ /// The function to merge the expressions.
+ /// An expression that represents the merged expressions.
+ private static Expression Compose(
+ this Expression left,
+ Expression right,
+ Func merge)
+ {
+ var map = left.Parameters
+ .Select((expr, index) => new { Expression = expr, Parameter = right.Parameters[index] })
+ .ToDictionary(p => p.Parameter, p => p.Expression);
+
+ var rightBody = ParameterRebinder.ReplaceParameters(map, right.Body);
+
+ return Expression.Lambda(merge(left.Body, rightBody), left.Parameters);
+ }
+}
diff --git a/Ctoss/Extensions/ParameterRebinder.cs b/Ctoss/Extensions/ParameterRebinder.cs
new file mode 100644
index 0000000..6f4c171
--- /dev/null
+++ b/Ctoss/Extensions/ParameterRebinder.cs
@@ -0,0 +1,45 @@
+using System.Linq.Expressions;
+
+namespace Ctoss.Extensions;
+
+///
+/// Rebinds parameters in expressions to new parameters.
+///
+public sealed class ParameterRebinder : ExpressionVisitor
+{
+ private readonly IDictionary _map;
+
+ ///
+ /// Initializes a new instance of the class with the specified parameter map.
+ ///
+ /// A dictionary that maps original parameters to replacement parameters.
+ private ParameterRebinder(IDictionary? map)
+ {
+ _map = map ?? new Dictionary();
+ }
+
+ ///
+ /// Replaces the parameters in the given expression according to the specified map.
+ ///
+ /// A dictionary that maps original parameters to replacement parameters.
+ /// The expression in which to replace parameters.
+ /// An expression with the parameters replaced.
+ public static Expression ReplaceParameters(
+ IDictionary map,
+ Expression expression)
+ {
+ return new ParameterRebinder(map).Visit(expression);
+ }
+
+ ///
+ /// Visits the nodes in the expression tree and replaces them according to the map.
+ ///
+ /// The parameter expression to visit.
+ /// The modified expression, if the parameter is replaced; otherwise, the original expression.
+ protected override Expression VisitParameter(ParameterExpression node)
+ {
+ if (_map.TryGetValue(node, out var replacement)) node = replacement;
+
+ return base.VisitParameter(node);
+ }
+}
diff --git a/Ctoss/FilterBuilder.cs b/Ctoss/FilterBuilder.cs
new file mode 100644
index 0000000..4ab1ed3
--- /dev/null
+++ b/Ctoss/FilterBuilder.cs
@@ -0,0 +1,203 @@
+using System.Linq.Expressions;
+using System.Text.Json;
+using Ctoss.Extensions;
+using Ctoss.Json;
+using Ctoss.Models;
+using Ctoss.Models.Conditions;
+using Ctoss.Models.Enums;
+
+namespace Ctoss;
+
+public class FilterBuilder
+{
+ private static readonly JsonSerializerOptions JsonOptions = new()
+ {
+ Converters =
+ {
+ new FilterConditionConverter(),
+ new JsonStringEnumConverter(),
+ new JsonStringEnumConverter(),
+ new JsonStringEnumConverter(),
+ new JsonStringEnumConverter()
+ },
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ };
+
+ public Expression>? GetExpression(Dictionary? filters)
+ {
+ if (filters == null)
+ return null;
+
+ var expressions = new List>>();
+
+ expressions.AddRange(filters.Select(filter => GetExpressionInternal(filter.Key, filter.Value)));
+ return expressions.Aggregate((acc, expr) => acc.AndAlso(expr));
+ }
+
+ public Expression>? GetExpression(string jsonFilter)
+ => GetExpression(JsonSerializer.Deserialize>(jsonFilter, JsonOptions));
+
+ public Expression>? GetExpression(string property, NumberFilter numberFilter)
+ => GetExpression(new Dictionary { { property, numberFilter } });
+
+ private Expression> GetExpressionInternal(string property, NumberFilter? filter)
+ {
+ if (filter == null)
+ return _ => true;
+
+ if (filter.Operator != Operator.NoOp)
+ {
+ return filter.Conditions?
+ .Select(c => GetFilterExpr(property, c))
+ .Aggregate((acc, expr) => filter.Operator switch
+ {
+ Operator.And => acc.AndAlso(expr),
+ Operator.Or => acc.OrElse(expr),
+ _ => throw new ArgumentOutOfRangeException()
+ })!;
+ }
+
+ return GetFilterExpr(property, filter.Condition1);
+ }
+
+ private static Expression> GetFilterExpr(string property, FilterCondition? condition)
+ {
+ return condition switch
+ {
+ TextFilterCondition textFilter => GetTextFilterExpr(property, textFilter),
+ NumberFilterCondition numberFilter => GetNumberFilterExpr(property, numberFilter),
+ DateFilterCondition dateFilter => GetDateFilterExpr(property, dateFilter),
+ _ => _ => true
+ };
+ }
+
+ private static Expression> GetTextFilterExpr(string property, TextFilterCondition condition)
+ {
+ var parameter = Expression.Parameter(typeof(T), "x");
+ var propertyExpression = Expression.Property(parameter, property);
+ var valueExpression = Expression.Constant(condition.Filter);
+
+ return condition.Type switch
+ {
+ TextFilterOptions.Contains => Expression.Lambda>(
+ Expression.Call(propertyExpression, nameof(string.Contains), null, valueExpression), parameter),
+ TextFilterOptions.NotContains => Expression.Lambda>(
+ Expression.Not(
+ Expression.Call(propertyExpression, nameof(string.Contains), null, valueExpression)), parameter),
+ TextFilterOptions.Equals => Expression.Lambda>(
+ Expression.Equal(propertyExpression, valueExpression), parameter),
+ TextFilterOptions.NotEquals => Expression.Lambda>(
+ Expression.NotEqual(propertyExpression, valueExpression), parameter),
+ TextFilterOptions.StartsWith => Expression.Lambda>(
+ Expression.Call(propertyExpression, nameof(string.StartsWith), null, valueExpression), parameter),
+ TextFilterOptions.EndsWith => Expression.Lambda>(
+ Expression.Call(propertyExpression, nameof(string.EndsWith), null, valueExpression), parameter),
+ TextFilterOptions.Blank => Expression.Lambda>(
+ Expression.Equal(propertyExpression, Expression.Constant(null, typeof(string))), parameter),
+ TextFilterOptions.NotBlank => Expression.Lambda>(
+ Expression.NotEqual(propertyExpression, Expression.Constant(null, typeof(string))), parameter),
+ _ => _ => true
+ };
+ }
+
+ private static Expression> GetNumberFilterExpr(string property, NumberFilterCondition numberFilter)
+ {
+ var parameter = Expression.Parameter(typeof(T), "x");
+ var propertyExpression = Expression.Property(parameter, property);
+
+ var propertyType = typeof(T).GetProperty(property)?.PropertyType;
+ if (propertyType == null)
+ throw new ArgumentException($"Property '{property}' not found on type '{typeof(T).Name}'");
+
+ var filterValue = Convert.ChangeType(numberFilter.Filter, propertyType);
+ var valueExpression = Expression.Constant(filterValue, propertyType);
+
+ switch (numberFilter.Type)
+ {
+ case NumberFilterOptions.Equals:
+ return Expression.Lambda>(
+ Expression.Equal(propertyExpression, valueExpression), parameter);
+ case NumberFilterOptions.NotEquals:
+ return Expression.Lambda>(
+ Expression.NotEqual(propertyExpression, valueExpression), parameter);
+ case NumberFilterOptions.GreaterThan:
+ return Expression.Lambda>(
+ Expression.GreaterThan(propertyExpression, valueExpression), parameter);
+ case NumberFilterOptions.GreaterThanOrEqual:
+ return Expression.Lambda>(
+ Expression.GreaterThanOrEqual(propertyExpression, valueExpression), parameter);
+ case NumberFilterOptions.LessThan:
+ return Expression.Lambda>(
+ Expression.LessThan(propertyExpression, valueExpression), parameter);
+ case NumberFilterOptions.LessThanOrEqual:
+ return Expression.Lambda>(
+ Expression.LessThanOrEqual(propertyExpression, valueExpression), parameter);
+ case NumberFilterOptions.InRange:
+ var filterToValue = Convert.ChangeType(numberFilter.FilterTo, propertyType);
+ var valueToExpression = Expression.Constant(filterToValue, propertyType);
+ var greaterThan = Expression.GreaterThan(propertyExpression, valueExpression);
+ var lessThan = Expression.LessThan(propertyExpression, valueToExpression);
+ return Expression.Lambda>(
+ Expression.AndAlso(greaterThan, lessThan), parameter);
+ case NumberFilterOptions.Blank:
+ return Expression.Lambda>(
+ Expression.Equal(propertyExpression, Expression.Constant(null, typeof(decimal?))), parameter);
+ case NumberFilterOptions.Empty:
+ case NumberFilterOptions.NotBlank:
+ return Expression.Lambda>(
+ Expression.NotEqual(propertyExpression, Expression.Constant(null, typeof(decimal?))), parameter);
+ default:
+ throw new NotSupportedException($"Number filter type '{numberFilter.Type}' is not supported.");
+ }
+ }
+
+ private static Expression> GetDateFilterExpr(string property, DateFilterCondition dateFilter)
+ {
+ var parameter = Expression.Parameter(typeof(T), "x");
+ var propertyExpression = Expression.Property(parameter, property);
+
+ ConstantExpression dateFromExpression = null!;
+
+ if (dateFilter.Type is not DateFilterOptions.Blank &&
+ dateFilter.Type is not DateFilterOptions.NotBlank &&
+ dateFilter.Type is not DateFilterOptions.Empty)
+ {
+ dateFromExpression = Expression.Constant(DateTime.Parse(dateFilter.DateFrom!), typeof(DateTime));
+ }
+
+ switch (dateFilter.Type)
+ {
+ case DateFilterOptions.Equals:
+ return Expression.Lambda>(
+ Expression.Equal(propertyExpression, dateFromExpression), parameter);
+ case DateFilterOptions.NotEquals:
+ return Expression.Lambda>(
+ Expression.NotEqual(propertyExpression, dateFromExpression), parameter);
+ case DateFilterOptions.LessThen:
+ return Expression.Lambda>(
+ Expression.LessThan(propertyExpression, dateFromExpression), parameter);
+ case DateFilterOptions.GreaterThen:
+ return Expression.Lambda>(
+ Expression.GreaterThan(propertyExpression, dateFromExpression), parameter);
+ case DateFilterOptions.InRange:
+ var dateToExpression = Expression.Constant(DateTime.Parse(dateFilter.DateTo!), typeof(DateTime));
+ var greaterThan = Expression.GreaterThan(propertyExpression, dateFromExpression);
+ var lessThan = Expression.LessThan(propertyExpression, dateToExpression);
+ return Expression.Lambda>(
+ Expression.AndAlso(greaterThan, lessThan), parameter);
+ case DateFilterOptions.Empty:
+ case DateFilterOptions.Blank:
+ return Expression.Lambda>(
+ Expression.Equal(
+ Expression.Convert(propertyExpression, typeof(DateTime?)),
+ Expression.Constant(null, typeof(DateTime?))), parameter);
+ case DateFilterOptions.NotBlank:
+ return Expression.Lambda>(
+ Expression.NotEqual(
+ Expression.Convert(propertyExpression, typeof(DateTime?)),
+ Expression.Constant(null, typeof(DateTime?))), parameter);
+ default:
+ throw new NotSupportedException($"Date filter type '{dateFilter.Type}' is not supported.");
+ }
+ }
+}
diff --git a/Ctoss/Json/FilterConditionConverter.cs b/Ctoss/Json/FilterConditionConverter.cs
new file mode 100644
index 0000000..48601ef
--- /dev/null
+++ b/Ctoss/Json/FilterConditionConverter.cs
@@ -0,0 +1,29 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Ctoss.Models.Conditions;
+
+namespace Ctoss.Json;
+
+public class FilterConditionConverter : JsonConverter
+{
+ public override FilterCondition Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ using var doc = JsonDocument.ParseValue(ref reader);
+ var root = doc.RootElement;
+ var filterType = root.GetProperty("filterType").GetString()
+ ?? throw new ArgumentException("filterType is required");
+
+ return (filterType switch
+ {
+ "text" => JsonSerializer.Deserialize(root.GetRawText(), options),
+ "number" => JsonSerializer.Deserialize(root.GetRawText(), options),
+ "date" => JsonSerializer.Deserialize(root.GetRawText(), options),
+ _ => throw new NotSupportedException($"FilterType {filterType} is not supported")
+ })!;
+ }
+
+ public override void Write(Utf8JsonWriter writer, FilterCondition value, JsonSerializerOptions options)
+ {
+ JsonSerializer.Serialize(writer, (object)value, options);
+ }
+}
diff --git a/Ctoss/Json/JsonStringEnumConverter.cs b/Ctoss/Json/JsonStringEnumConverter.cs
new file mode 100644
index 0000000..008cd0e
--- /dev/null
+++ b/Ctoss/Json/JsonStringEnumConverter.cs
@@ -0,0 +1,28 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Ctoss.Json;
+
+public class JsonStringEnumConverter : JsonConverter where T : struct, Enum
+{
+ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.String)
+ {
+ throw new JsonException();
+ }
+
+ var enumValue = reader.GetString();
+ if (Enum.TryParse(enumValue, true, out T value))
+ {
+ return value;
+ }
+
+ throw new JsonException($"Unable to convert \"{enumValue}\" to enum \"{typeof(T)}\".");
+ }
+
+ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.ToString());
+ }
+}
diff --git a/Ctoss/Models/Conditions/DateFilterCondition.cs b/Ctoss/Models/Conditions/DateFilterCondition.cs
new file mode 100644
index 0000000..dc9a841
--- /dev/null
+++ b/Ctoss/Models/Conditions/DateFilterCondition.cs
@@ -0,0 +1,10 @@
+using Ctoss.Models.Enums;
+
+namespace Ctoss.Models.Conditions;
+
+public class DateFilterCondition : FilterCondition
+{
+ public string? DateFrom { get; set; }
+ public string? DateTo { get; set; }
+ public DateFilterOptions? Type { get; set; }
+}
diff --git a/Ctoss/Models/Conditions/FilterCondition.cs b/Ctoss/Models/Conditions/FilterCondition.cs
new file mode 100644
index 0000000..880a76e
--- /dev/null
+++ b/Ctoss/Models/Conditions/FilterCondition.cs
@@ -0,0 +1,6 @@
+namespace Ctoss.Models.Conditions;
+
+public class FilterCondition
+{
+ public string FilterType { get; set; }
+}
diff --git a/Ctoss/Models/Conditions/NumberFilterCondition.cs b/Ctoss/Models/Conditions/NumberFilterCondition.cs
new file mode 100644
index 0000000..7f777b5
--- /dev/null
+++ b/Ctoss/Models/Conditions/NumberFilterCondition.cs
@@ -0,0 +1,10 @@
+using Ctoss.Models.Enums;
+
+namespace Ctoss.Models.Conditions;
+
+public class NumberFilterCondition : FilterCondition
+{
+ public decimal? Filter { get; set; }
+ public decimal? FilterTo { get; set; }
+ public NumberFilterOptions? Type { get; set; }
+}
diff --git a/Ctoss/Models/Conditions/TextFilterCondition.cs b/Ctoss/Models/Conditions/TextFilterCondition.cs
new file mode 100644
index 0000000..7ea014d
--- /dev/null
+++ b/Ctoss/Models/Conditions/TextFilterCondition.cs
@@ -0,0 +1,7 @@
+namespace Ctoss.Models.Conditions;
+
+public class TextFilterCondition : FilterCondition
+{
+ public string? Filter { get; set; }
+ public Enums.TextFilterOptions Type { get; set; }
+}
diff --git a/Ctoss/Models/Enums/DateFilterOptions.cs b/Ctoss/Models/Enums/DateFilterOptions.cs
new file mode 100644
index 0000000..45eafaf
--- /dev/null
+++ b/Ctoss/Models/Enums/DateFilterOptions.cs
@@ -0,0 +1,63 @@
+namespace Ctoss.Models.Enums;
+
+///
+/// Specifies the available options for date filtering.
+///
+public enum DateFilterOptions
+{
+ ///
+ /// Provides an option to choose one of the other filter options.
+ /// Option Key: empty
+ /// Included by Default: No
+ ///
+ Empty = 0,
+
+ ///
+ /// Filters for dates that are equal to the specified date.
+ /// Option Key: equals
+ /// Included by Default: Yes
+ ///
+ Equals = 1,
+
+ ///
+ /// Filters for dates that are not equal to the specified date.
+ /// Option Key: notEqual
+ /// Included by Default: Yes
+ ///
+ NotEquals = 2,
+
+ ///
+ /// Filters for dates that are less than the specified date.
+ /// Option Key: lessThan
+ /// Included by Default: Yes
+ ///
+ LessThen = 3,
+
+ ///
+ /// Filters for dates that are greater than the specified date.
+ /// Option Key: greaterThan
+ /// Included by Default: Yes
+ ///
+ GreaterThen = 4,
+
+ ///
+ /// Filters for dates that are within a specified range.
+ /// Option Key: inRange
+ /// Included by Default: Yes
+ ///
+ InRange = 5,
+
+ ///
+ /// Filters for blank (null or empty) dates.
+ /// Option Key: blank
+ /// Included by Default: Yes
+ ///
+ Blank = 6,
+
+ ///
+ /// Filters for dates that are not blank (not null or empty).
+ /// Option Key: notBlank
+ /// Included by Default: Yes
+ ///
+ NotBlank = 7
+}
diff --git a/Ctoss/Models/Enums/NumberFilterOptions.cs b/Ctoss/Models/Enums/NumberFilterOptions.cs
new file mode 100644
index 0000000..399d6d6
--- /dev/null
+++ b/Ctoss/Models/Enums/NumberFilterOptions.cs
@@ -0,0 +1,77 @@
+namespace Ctoss.Models.Enums;
+
+///
+/// Specifies the available options for number filtering.
+///
+public enum NumberFilterOptions
+{
+ ///
+ /// Provides an option to choose one of the other filter options.
+ /// Option Key: empty
+ /// Included by Default: No
+ ///
+ Empty = 0,
+
+ ///
+ /// Filters for numbers that are equal to the specified value.
+ /// Option Key: equals
+ /// Included by Default: Yes
+ ///
+ Equals = 1,
+
+ ///
+ /// Filters for numbers that are not equal to the specified value.
+ /// Option Key: notEqual
+ /// Included by Default: Yes
+ ///
+ NotEquals = 2,
+
+ ///
+ /// Filters for numbers that are greater than the specified value.
+ /// Option Key: greaterThan
+ /// Included by Default: Yes
+ ///
+ GreaterThan = 3,
+
+ ///
+ /// Filters for numbers that are greater than or equal to the specified value.
+ /// Option Key: greaterThanOrEqual
+ /// Included by Default: Yes
+ ///
+ GreaterThanOrEqual = 4,
+
+ ///
+ /// Filters for numbers that are less than the specified value.
+ /// Option Key: lessThan
+ /// Included by Default: Yes
+ ///
+ LessThan = 5,
+
+ ///
+ /// Filters for numbers that are less than or equal to the specified value.
+ /// Option Key: lessThanOrEqual
+ /// Included by Default: Yes
+ ///
+ LessThanOrEqual = 6,
+
+ ///
+ /// Filters for numbers that are within a specified range.
+ /// Option Key: inRange
+ /// Included by Default: Yes
+ ///
+ InRange = 7,
+
+ ///
+ /// Filters for blank (null or empty) numbers.
+ /// Option Key: blank
+ /// Included by Default: Yes
+ ///
+ Blank = 8,
+
+ ///
+ /// Filters for numbers that are not blank (not null or empty).
+ /// Option Key: notBlank
+ /// Included by Default: Yes
+ ///
+ NotBlank = 9
+}
diff --git a/Ctoss/Models/Enums/Operator.cs b/Ctoss/Models/Enums/Operator.cs
new file mode 100644
index 0000000..e41c0fb
--- /dev/null
+++ b/Ctoss/Models/Enums/Operator.cs
@@ -0,0 +1,24 @@
+namespace Ctoss.Models.Enums;
+
+///
+/// Specifies the logical operators for combining filter conditions.
+///
+public enum Operator
+{
+ ///
+ /// No logical operator applied.
+ ///
+ NoOp = 0,
+
+ ///
+ /// Logical AND operator. Both conditions must be true.
+ /// Option Key: and
+ ///
+ And = 1,
+
+ ///
+ /// Logical OR operator. At least one condition must be true.
+ /// Option Key: or
+ ///
+ Or = 2
+}
diff --git a/Ctoss/Models/Enums/TextFilterOptions.cs b/Ctoss/Models/Enums/TextFilterOptions.cs
new file mode 100644
index 0000000..83d2c2f
--- /dev/null
+++ b/Ctoss/Models/Enums/TextFilterOptions.cs
@@ -0,0 +1,70 @@
+namespace Ctoss.Models.Enums;
+
+///
+/// Specifies the available options for text filtering.
+///
+public enum TextFilterOptions
+{
+ ///
+ /// Provides an option to choose one of the other filter options.
+ /// Option Key: empty
+ /// Included by Default: No
+ ///
+ Empty = 0,
+
+ ///
+ /// Filters for text that contains the specified value.
+ /// Option Key: contains
+ /// Included by Default: Yes
+ ///
+ Contains = 1,
+
+ ///
+ /// Filters for text that does not contain the specified value.
+ /// Option Key: notContains
+ /// Included by Default: Yes
+ ///
+ NotContains = 2,
+
+ ///
+ /// Filters for text that is equal to the specified value.
+ /// Option Key: equals
+ /// Included by Default: Yes
+ ///
+ Equals = 3,
+
+ ///
+ /// Filters for text that is not equal to the specified value.
+ /// Option Key: notEqual
+ /// Included by Default: Yes
+ ///
+ NotEquals = 4,
+
+ ///
+ /// Filters for text that starts with the specified value.
+ /// Option Key: startsWith
+ /// Included by Default: Yes
+ ///
+ StartsWith = 5,
+
+ ///
+ /// Filters for text that ends with the specified value.
+ /// Option Key: endsWith
+ /// Included by Default: Yes
+ ///
+ EndsWith = 6,
+
+ ///
+ /// Filters for blank (null or empty) text.
+ /// Option Key: blank
+ /// Included by Default: Yes
+ ///
+ Blank = 7,
+
+ ///
+ /// Filters for text that is not blank (not null or empty).
+ /// Option Key: notBlank
+ /// Included by Default: Yes
+ ///
+ NotBlank = 8
+}
diff --git a/Ctoss/Models/NumberFilter.cs b/Ctoss/Models/NumberFilter.cs
new file mode 100644
index 0000000..7f0a8f6
--- /dev/null
+++ b/Ctoss/Models/NumberFilter.cs
@@ -0,0 +1,13 @@
+using Ctoss.Models.Conditions;
+using Ctoss.Models.Enums;
+
+namespace Ctoss.Models;
+
+public class NumberFilter
+{
+ public string FilterType { get; set; } = null!;
+ public Operator Operator { get; set; }
+ public FilterCondition? Condition1 { get; set; }
+ public FilterCondition? Condition2 { get; set; }
+ public List? Conditions { get; set; }
+}