Compare commits

..

No commits in common. "master" and "v2.5.3" have entirely different histories.

16 changed files with 40 additions and 436 deletions

View File

@ -129,8 +129,7 @@ Then you can add the configuration:
"DefaultPageSize": "int number: optional number to fallback to when no page argument is given. Set <=0 to disable paging if no pageSize is specified (default).", "DefaultPageSize": "int number: optional number to fallback to when no page argument is given. Set <=0 to disable paging if no pageSize is specified (default).",
"MaxPageSize": "int number: maximum allowed page size. Set <=0 to make infinite (default)", "MaxPageSize": "int number: maximum allowed page size. Set <=0 to make infinite (default)",
"ThrowExceptions": "boolean: should Sieve throw exceptions instead of silently failing? Defaults to false", "ThrowExceptions": "boolean: should Sieve throw exceptions instead of silently failing? Defaults to false",
"IgnoreNullsOnNotEqual": "boolean: ignore null values when filtering using is not equal operator? Defaults to true", "IgnoreNullsOnNotEqual": "boolean: ignore null values when filtering using is not equal operator? Default to true"
"DisableNullableTypeExpressionForSorting": "boolean: disable the creation of nullable type expression for sorting. Some databases do not handle it (yet). Defaults to false"
} }
} }
``` ```
@ -208,13 +207,10 @@ You can replace this DSL with your own (eg. use JSON instead) by implementing an
| `<=` | Less than or equal to | | `<=` | Less than or equal to |
| `@=` | Contains | | `@=` | Contains |
| `_=` | Starts with | | `_=` | Starts with |
| `_-=` | Ends with |
| `!@=` | Does not Contains | | `!@=` | Does not Contains |
| `!_=` | Does not Starts with | | `!_=` | Does not Starts with |
| `!_-=` | Does not Ends with |
| `@=*` | Case-insensitive string Contains | | `@=*` | Case-insensitive string Contains |
| `_=*` | Case-insensitive string Starts with | | `_=*` | Case-insensitive string Starts with |
| `_-=*` | Case-insensitive string Ends with |
| `==*` | Case-insensitive string Equals | | `==*` | Case-insensitive string Equals |
| `!=*` | Case-insensitive string Not equals | | `!=*` | Case-insensitive string Not equals |
| `!@=*` | Case-insensitive string does not Contains | | `!@=*` | Case-insensitive string does not Contains |
@ -235,7 +231,7 @@ It is recommended that you write exception-handling middleware to globally handl
You can find an example project incorporating most Sieve concepts in [SieveTests](https://github.com/Biarity/Sieve/tree/master/SieveTests). You can find an example project incorporating most Sieve concepts in [SieveTests](https://github.com/Biarity/Sieve/tree/master/SieveTests).
## Fluent API ## Fluent API
To use the Fluent API instead of attributes in marking properties, setup an alternative `SieveProcessor` that overrides `MapProperties`. For [example](https://github.com/Biarity/Sieve/blob/master/Sieve.Sample/Services/ApplicationSieveProcessor.cs): To use the Fluent API instead of attributes in marking properties, setup an alternative `SieveProcessor` that overrides `MapProperties`. For example:
```C# ```C#
public class ApplicationSieveProcessor : SieveProcessor public class ApplicationSieveProcessor : SieveProcessor
@ -267,78 +263,13 @@ public class ApplicationSieveProcessor : SieveProcessor
} }
``` ```
Now you should inject the new class instead: Now you should inject the new class instead:
```C# ```C#
services.AddScoped<ISieveProcessor, ApplicationSieveProcessor>(); services.AddScoped<ISieveProcessor, ApplicationSieveProcessor>();
``` ```
Find More on Sieve's Fluent API [here](https://github.com/Biarity/Sieve/issues/4#issuecomment-364629048). Find More on Sieve's Fluent API [here](https://github.com/Biarity/Sieve/issues/4#issuecomment-364629048).
### Modular Fluent API configuration
Adding all fluent mappings directly in the processor can become unwieldy on larger projects.
It can also clash with vertical architectures.
To enable functional grouping of mappings the `ISieveConfiguration` interface was created together with extensions to the default mapper.
```C#
public class SieveConfigurationForPost : ISieveConfiguration
{
public void Configure(SievePropertyMapper mapper)
{
mapper.Property<Post>(p => p.Title)
.CanFilter()
.HasName("a_different_query_name_here");
mapper.Property<Post>(p => p.CommentCount)
.CanSort();
mapper.Property<Post>(p => p.DateCreated)
.CanSort()
.CanFilter()
.HasName("created_on");
return mapper;
}
}
```
With the processor simplified to:
```C#
public class ApplicationSieveProcessor : SieveProcessor
{
public ApplicationSieveProcessor(
IOptions<SieveOptions> options,
ISieveCustomSortMethods customSortMethods,
ISieveCustomFilterMethods customFilterMethods)
: base(options, customSortMethods, customFilterMethods)
{
}
protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper)
{
return mapper
.ApplyConfiguration<SieveConfigurationForPost>()
.ApplyConfiguration<SieveConfigurationForComment>();
}
}
```
There is also the option to scan and add all configurations for a given assembly
```C#
public class ApplicationSieveProcessor : SieveProcessor
{
public ApplicationSieveProcessor(
IOptions<SieveOptions> options,
ISieveCustomSortMethods customSortMethods,
ISieveCustomFilterMethods customFilterMethods)
: base(options, customSortMethods, customFilterMethods)
{
}
protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper)
{
return mapper.ApplyConfigurationsFromAssembly(typeof(ApplicationSieveProcessor).Assembly);
}
}
```
## Upgrading to v2.2.0 ## Upgrading to v2.2.0
2.2.0 introduced OR logic for filter values. This means your custom filters will need to accept multiple values rather than just the one. 2.2.0 introduced OR logic for filter values. This means your custom filters will need to accept multiple values rather than just the one.

View File

@ -1,15 +0,0 @@
using Sieve.Services;
namespace Sieve.Sample.Entities
{
public class SieveConfigurationForPost : ISieveConfiguration
{
public void Configure(SievePropertyMapper mapper)
{
mapper.Property<Post>(p => p.Title)
.CanSort()
.CanFilter()
.HasName("CustomTitleName");
}
}
}

View File

@ -13,18 +13,11 @@ namespace Sieve.Sample.Services
protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper) protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper)
{ {
// Option 1: Map all properties centrally
mapper.Property<Post>(p => p.Title) mapper.Property<Post>(p => p.Title)
.CanSort() .CanSort()
.CanFilter() .CanFilter()
.HasName("CustomTitleName"); .HasName("CustomTitleName");
// Option 2: Manually apply functionally grouped mapping configurations
//mapper.ApplyConfiguration<SieveConfigurationForPost>();
// Option 3: Scan and apply all configurations
//mapper.ApplyConfigurationsFromAssembly(typeof(ApplicationSieveProcessor).Assembly);
return mapper; return mapper;
} }
} }

View File

@ -12,10 +12,9 @@ namespace Sieve.Extensions
string fullPropertyName, string fullPropertyName,
PropertyInfo propertyInfo, PropertyInfo propertyInfo,
bool desc, bool desc,
bool useThenBy, bool useThenBy)
bool disableNullableTypeExpression = false)
{ {
var lambda = GenerateLambdaWithSafeMemberAccess<TEntity>(fullPropertyName, propertyInfo, disableNullableTypeExpression); var lambda = GenerateLambdaWithSafeMemberAccess<TEntity>(fullPropertyName, propertyInfo);
var command = desc var command = desc
? (useThenBy ? "ThenByDescending" : "OrderByDescending") ? (useThenBy ? "ThenByDescending" : "OrderByDescending")
@ -34,8 +33,7 @@ namespace Sieve.Extensions
private static Expression<Func<TEntity, object>> GenerateLambdaWithSafeMemberAccess<TEntity> private static Expression<Func<TEntity, object>> GenerateLambdaWithSafeMemberAccess<TEntity>
( (
string fullPropertyName, string fullPropertyName,
PropertyInfo propertyInfo, PropertyInfo propertyInfo
bool disableNullableTypeExpression
) )
{ {
var parameter = Expression.Parameter(typeof(TEntity), "e"); var parameter = Expression.Parameter(typeof(TEntity), "e");
@ -54,7 +52,7 @@ namespace Sieve.Extensions
propertyValue = Expression.MakeMemberAccess(propertyValue, propertyInfo); propertyValue = Expression.MakeMemberAccess(propertyValue, propertyInfo);
} }
if (propertyValue.Type.IsNullable() && !disableNullableTypeExpression) if (propertyValue.Type.IsNullable())
{ {
nullCheck = GenerateOrderNullCheckExpression(propertyValue, nullCheck); nullCheck = GenerateOrderNullCheckExpression(propertyValue, nullCheck);
} }

View File

@ -10,6 +10,5 @@
LessThanOrEqualTo, LessThanOrEqualTo,
Contains, Contains,
StartsWith, StartsWith,
EndsWith,
} }
} }

View File

@ -8,7 +8,7 @@ namespace Sieve.Models
public class FilterTerm : IFilterTerm, IEquatable<FilterTerm> public class FilterTerm : IFilterTerm, IEquatable<FilterTerm>
{ {
private const string EscapedPipePattern = @"(?<!($|[^\\]|^)(\\\\)*?\\)\|"; private const string EscapedPipePattern = @"(?<!($|[^\\]|^)(\\\\)*?\\)\|";
private const string OperatorsRegEx = @"(!@=\*|!_=\*|!_-=\*|!=\*|!@=|!_=|!_-=|==\*|@=\*|_=\*|_-=\*|==|!=|>=|<=|>|<|@=|_=|_-=)"; private const string OperatorsRegEx = @"(!@=\*|!_=\*|!=\*|!@=|!_=|==\*|@=\*|_=\*|==|!=|>=|<=|>|<|@=|_=)";
private const string EscapeNegPatternForOper = @"(?<!\\)" + OperatorsRegEx; private const string EscapeNegPatternForOper = @"(?<!\\)" + OperatorsRegEx;
private const string EscapePosPatternForOper = @"(?<=\\)" + OperatorsRegEx; private const string EscapePosPatternForOper = @"(?<=\\)" + OperatorsRegEx;
@ -22,13 +22,13 @@ namespace Sieve.Models
{ {
set set
{ {
var filterSplits = Regex.Split(value, EscapeNegPatternForOper).Select(t => t.Trim()).ToArray(); var filterSplits = Regex.Split(value,EscapeNegPatternForOper).Select(t => t.Trim()).ToArray();
Names = Regex.Split(filterSplits[0], EscapedPipePattern).Select(t => t.Trim()).ToArray(); Names = Regex.Split(filterSplits[0], EscapedPipePattern).Select(t => t.Trim()).ToArray();
if (filterSplits.Length > 2) if (filterSplits.Length > 2)
{ {
foreach (var match in Regex.Matches(filterSplits[2], EscapePosPatternForOper)) foreach (var match in Regex.Matches(filterSplits[2],EscapePosPatternForOper))
{ {
var matchStr = match.ToString(); var matchStr = match.ToString();
filterSplits[2] = filterSplits[2].Replace('\\' + matchStr, matchStr); filterSplits[2] = filterSplits[2].Replace('\\' + matchStr, matchStr);
@ -39,7 +39,7 @@ namespace Sieve.Models
.ToArray(); .ToArray();
} }
Operator = Regex.Match(value, EscapeNegPatternForOper).Value; Operator = Regex.Match(value,EscapeNegPatternForOper).Value;
OperatorParsed = GetOperatorParsed(Operator); OperatorParsed = GetOperatorParsed(Operator);
OperatorIsCaseInsensitive = Operator.EndsWith("*"); OperatorIsCaseInsensitive = Operator.EndsWith("*");
OperatorIsNegated = OperatorParsed != FilterOperator.NotEquals && Operator.StartsWith("!"); OperatorIsNegated = OperatorParsed != FilterOperator.NotEquals && Operator.StartsWith("!");
@ -80,9 +80,6 @@ namespace Sieve.Models
case "_=": case "_=":
case "!_=": case "!_=":
return FilterOperator.StartsWith; return FilterOperator.StartsWith;
case "_-=":
case "!_-=":
return FilterOperator.EndsWith;
default: default:
return FilterOperator.Equals; return FilterOperator.Equals;
} }

View File

@ -11,7 +11,5 @@
public bool ThrowExceptions { get; set; } = false; public bool ThrowExceptions { get; set; } = false;
public bool IgnoreNullsOnNotEqual { get; set; } = true; public bool IgnoreNullsOnNotEqual { get; set; } = true;
public bool DisableNullableTypeExpressionForSorting { get; set; } = false;
} }
} }

View File

@ -1,70 +0,0 @@
#nullable enable
using System;
using System.Linq;
using System.Reflection;
namespace Sieve.Services
{
/// <summary>
/// Use this interface to create SieveConfiguration (just like EntityTypeConfigurations are defined for EF)
/// </summary>
public interface ISieveConfiguration
{
/// <summary>
/// Configures sieve property mappings.
/// </summary>
/// <param name="mapper"> The mapper used to configure the sieve properties on. </param>
void Configure(SievePropertyMapper mapper);
}
/// <summary>
/// Configuration extensions to the <see cref="SievePropertyMapper" />
/// </summary>
public static class SieveConfigurationExtensions
{
/// <summary>
/// Applies configuration that is defined in an <see cref="ISieveConfiguration" /> instance.
/// </summary>
/// <param name="mapper"> The mapper to apply the configuration on. </param>
/// <typeparam name="T">The configuration to be applied. </typeparam>
/// <returns>
/// The same <see cref="SievePropertyMapper" /> instance so that additional configuration calls can be chained.
/// </returns>
public static SievePropertyMapper ApplyConfiguration<T>(this SievePropertyMapper mapper) where T : ISieveConfiguration, new()
{
var configuration = new T();
configuration.Configure(mapper);
return mapper;
}
/// <summary>
/// Applies configuration from all <see cref="ISieveConfiguration" />
/// instances that are defined in provided assembly.
/// </summary>
/// <param name="mapper"> The mapper to apply the configuration on. </param>
/// <param name="assembly"> The assembly to scan. </param>
/// <returns>
/// The same <see cref="SievePropertyMapper" /> instance so that additional configuration calls can be chained.
/// </returns>
public static SievePropertyMapper ApplyConfigurationsFromAssembly(this SievePropertyMapper mapper, Assembly assembly)
{
foreach (var type in assembly.GetTypes().Where(t => !t.IsAbstract && !t.IsGenericTypeDefinition))
{
// Only accept types that contain a parameterless constructor, are not abstract.
var noArgConstructor = type.GetConstructor(Type.EmptyTypes);
if (noArgConstructor is null)
{
continue;
}
if (type.GetInterfaces().Any(t => t == typeof(ISieveConfiguration)))
{
var configuration = (ISieveConfiguration)noArgConstructor.Invoke(new object?[] { });
configuration.Configure(mapper);
}
}
return mapper;
}
}
}

View File

@ -339,9 +339,6 @@ namespace Sieve.Services
FilterOperator.StartsWith => Expression.Call(propertyValue, FilterOperator.StartsWith => Expression.Call(propertyValue,
typeof(string).GetMethods().First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1), typeof(string).GetMethods().First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1),
filterValue), filterValue),
FilterOperator.EndsWith => Expression.Call(propertyValue,
typeof(string).GetMethods().First(m => m.Name == "EndsWith" && m.GetParameters().Length == 1),
filterValue),
_ => Expression.Equal(propertyValue, filterValue) _ => Expression.Equal(propertyValue, filterValue)
}; };
} }
@ -368,7 +365,7 @@ namespace Sieve.Services
if (property != null) if (property != null)
{ {
result = result.OrderByDynamic(fullName, property, sortTerm.Descending, useThenBy, Options.Value.DisableNullableTypeExpressionForSorting); result = result.OrderByDynamic(fullName, property, sortTerm.Descending, useThenBy);
} }
else else
{ {

View File

@ -1,37 +0,0 @@
using Sieve.Services;
namespace SieveUnitTests.Abstractions.Entity
{
public class SieveConfigurationForIPost : ISieveConfiguration
{
public void Configure(SievePropertyMapper mapper)
{
mapper.Property<IPost>(p => p.ThisHasNoAttributeButIsAccessible)
.CanSort()
.CanFilter()
.HasName("shortname");
mapper.Property<IPost>(p => p.TopComment.Text)
.CanFilter();
mapper.Property<IPost>(p => p.TopComment.Id)
.CanSort();
mapper.Property<IPost>(p => p.OnlySortableViaFluentApi)
.CanSort();
mapper.Property<IPost>(p => p.TopComment.Text)
.CanFilter()
.HasName("topc");
mapper.Property<IPost>(p => p.FeaturedComment.Text)
.CanFilter()
.HasName("featc");
mapper
.Property<IPost>(p => p.DateCreated)
.CanSort()
.HasName("CreateDate");
}
}
}

View File

@ -1,37 +0,0 @@
using Sieve.Services;
namespace SieveUnitTests.Entities
{
public class SieveConfigurationForPost : ISieveConfiguration
{
public void Configure(SievePropertyMapper mapper)
{
mapper.Property<Post>(p => p.ThisHasNoAttributeButIsAccessible)
.CanSort()
.CanFilter()
.HasName("shortname");
mapper.Property<Post>(p => p.TopComment.Text)
.CanFilter();
mapper.Property<Post>(p => p.TopComment.Id)
.CanSort();
mapper.Property<Post>(p => p.OnlySortableViaFluentApi)
.CanSort();
mapper.Property<Post>(p => p.TopComment.Text)
.CanFilter()
.HasName("topc");
mapper.Property<Post>(p => p.FeaturedComment.Text)
.CanFilter()
.HasName("featc");
mapper
.Property<Post>(p => p.DateCreated)
.CanSort()
.HasName("CreateDate");
}
}
}

View File

@ -73,16 +73,6 @@ namespace SieveUnitTests
CategoryId = 2, CategoryId = 2,
TopComment = new Comment { Id = 1, Text = "D1" }, TopComment = new Comment { Id = 1, Text = "D1" },
FeaturedComment = new Comment { Id = 7, Text = "D2" } FeaturedComment = new Comment { Id = 7, Text = "D2" }
},
new Post
{
Id = 4,
Title = "Yen",
LikeCount = 5,
IsDraft = true,
CategoryId = 5,
TopComment = new Comment { Id = 4, Text = "Yen3" },
FeaturedComment = new Comment { Id = 8, Text = "Yen4" }
} }
}.AsQueryable(); }.AsQueryable();
@ -134,43 +124,7 @@ namespace SieveUnitTests
var result = _processor.Apply(model, _posts); var result = _processor.Apply(model, _posts);
Assert.Equal(1, result.First().Id); Assert.Equal(1, result.First().Id);
Assert.True(result.Count() == 4); Assert.True(result.Count() == 3);
}
[Fact]
public void EndsWithWorks()
{
var model = new SieveModel
{
Filters = "Title_-=n"
};
_testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Values.ToString());
_testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Operator);
_testOutputHelper.WriteLine(model.GetFiltersParsed()[0].OperatorParsed.ToString());
var result = _processor.Apply(model, _posts);
Assert.Equal(4, result.First().Id);
Assert.True(result.Count() == 1);
}
[Fact]
public void EndsWithCanBeCaseInsensitive()
{
var model = new SieveModel
{
Filters = "Title_-=*N"
};
_testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Values.ToString());
_testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Operator);
_testOutputHelper.WriteLine(model.GetFiltersParsed()[0].OperatorParsed.ToString());
var result = _processor.Apply(model, _posts);
Assert.Equal(4, result.First().Id);
Assert.True(result.Count() == 1);
} }
[Fact] [Fact]
@ -196,7 +150,7 @@ namespace SieveUnitTests
var result = _processor.Apply(model, _posts); var result = _processor.Apply(model, _posts);
Assert.True(result.Count() == 4); Assert.True(result.Count() == 3);
} }
[Fact] [Fact]
@ -251,8 +205,8 @@ namespace SieveUnitTests
var result = _processor.Apply(model, _posts); var result = _processor.Apply(model, _posts);
var nullableResult = _nullableProcessor.Apply(model, _posts); var nullableResult = _nullableProcessor.Apply(model, _posts);
Assert.True(result.Count() == 2); Assert.True(result.Count() == 1);
Assert.True(nullableResult.Count() == 3); Assert.True(nullableResult.Count() == 2);
} }
[Theory] [Theory]
@ -301,7 +255,7 @@ namespace SieveUnitTests
var result = _processor.Apply(model, _posts); var result = _processor.Apply(model, _posts);
Assert.False(result.Any(p => p.Id == 0)); Assert.False(result.Any(p => p.Id == 0));
Assert.True(result.Count() == 4); Assert.True(result.Count() == 3);
} }
[Fact] [Fact]
@ -520,12 +474,11 @@ namespace SieveUnitTests
}; };
var result = _processor.Apply(model, _posts); var result = _processor.Apply(model, _posts);
Assert.Equal(4, result.Count()); Assert.Equal(3, result.Count());
var posts = result.ToList(); var posts = result.ToList();
Assert.Contains("B", posts[0].TopComment.Text); Assert.Contains("B", posts[0].TopComment.Text);
Assert.Contains("C", posts[1].TopComment.Text); Assert.Contains("C", posts[1].TopComment.Text);
Assert.Contains("D", posts[2].TopComment.Text); Assert.Contains("D", posts[2].TopComment.Text);
Assert.Contains("Yen", posts[3].TopComment.Text);
} }
[Fact] [Fact]
@ -537,13 +490,12 @@ namespace SieveUnitTests
}; };
var result = _processor.Apply(model, _posts); var result = _processor.Apply(model, _posts);
Assert.Equal(5, result.Count()); Assert.Equal(4, result.Count());
var posts = result.ToList(); var posts = result.ToList();
Assert.Equal(0, posts[0].Id); Assert.Equal(0, posts[0].Id);
Assert.Equal(3, posts[1].Id); Assert.Equal(3, posts[1].Id);
Assert.Equal(2, posts[2].Id); Assert.Equal(2, posts[2].Id);
Assert.Equal(1, posts[3].Id); Assert.Equal(1, posts[3].Id);
Assert.Equal(4, posts[4].Id);
} }
[Fact] [Fact]
@ -678,15 +630,13 @@ namespace SieveUnitTests
}; };
var result = _processor.Apply(model, _posts); var result = _processor.Apply(model, _posts);
Assert.Equal(5, result.Count()); Assert.Equal(4, result.Count());
var posts = result.ToList(); var posts = result.ToList();
Assert.Equal(4, posts[0].Id); Assert.Equal(3,posts[0].Id);
Assert.Equal(3,posts[1].Id); Assert.Equal(2,posts[1].Id);
Assert.Equal(2,posts[2].Id); Assert.Equal(1,posts[2].Id);
Assert.Equal(1,posts[3].Id); Assert.Equal(0,posts[3].Id);
Assert.Equal(0,posts[4].Id);
} }
[Fact] [Fact]
@ -809,13 +759,10 @@ namespace SieveUnitTests
[InlineData(@"Title@=\>= ")] [InlineData(@"Title@=\>= ")]
[InlineData(@"Title@=\@= ")] [InlineData(@"Title@=\@= ")]
[InlineData(@"Title@=\_= ")] [InlineData(@"Title@=\_= ")]
[InlineData(@"Title@=\_-= ")]
[InlineData(@"Title@=!\@= ")] [InlineData(@"Title@=!\@= ")]
[InlineData(@"Title@=!\_= ")] [InlineData(@"Title@=!\_= ")]
[InlineData(@"Title@=!\_-= ")]
[InlineData(@"Title@=\@=* ")] [InlineData(@"Title@=\@=* ")]
[InlineData(@"Title@=\_=* ")] [InlineData(@"Title@=\_=* ")]
[InlineData(@"Title@=\_-=* ")]
[InlineData(@"Title@=\==* ")] [InlineData(@"Title@=\==* ")]
[InlineData(@"Title@=\!=* ")] [InlineData(@"Title@=\!=* ")]
[InlineData(@"Title@=!\@=* ")] [InlineData(@"Title@=!\@=* ")]
@ -826,7 +773,7 @@ namespace SieveUnitTests
new Post new Post
{ {
Id = 1, Id = 1,
Title = @"Operators: == != > < >= <= @= _= _-= !@= !_= !_-= @=* _=* ==* !=* !@=* !_=* !_-=* ", Title = @"Operators: == != > < >= <= @= _= !@= !_= @=* _=* ==* !=* !@=* !_=* ",
LikeCount = 1, LikeCount = 1,
IsDraft = true, IsDraft = true,
CategoryId = 1, CategoryId = 1,

View File

@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Sieve.Exceptions; using Sieve.Exceptions;
using Sieve.Models; using Sieve.Models;
using Sieve.Services;
using SieveUnitTests.Entities; using SieveUnitTests.Entities;
using SieveUnitTests.Services; using SieveUnitTests.Services;
using Xunit; using Xunit;
@ -11,10 +10,15 @@ namespace SieveUnitTests
{ {
public class Mapper public class Mapper
{ {
private readonly ApplicationSieveProcessor _processor;
private readonly IQueryable<Post> _posts; private readonly IQueryable<Post> _posts;
public Mapper() public Mapper()
{ {
_processor = new ApplicationSieveProcessor(new SieveOptionsAccessor(),
new SieveCustomSortMethods(),
new SieveCustomFilterMethods());
_posts = new List<Post> _posts = new List<Post>
{ {
new Post new Post
@ -41,49 +45,23 @@ namespace SieveUnitTests
}.AsQueryable(); }.AsQueryable();
} }
/// <summary> [Fact]
/// Processors with the same mappings but configured via a different method. public void MapperWorks()
/// </summary>
/// <returns></returns>
public static IEnumerable<object[]> GetProcessors()
{
yield return new object[] {
new ApplicationSieveProcessor(
new SieveOptionsAccessor(),
new SieveCustomSortMethods(),
new SieveCustomFilterMethods())};
yield return new object[] {
new ModularConfigurationSieveProcessor(
new SieveOptionsAccessor(),
new SieveCustomSortMethods(),
new SieveCustomFilterMethods())};
yield return new object[] {
new ModularConfigurationWithScanSieveProcessor(
new SieveOptionsAccessor(),
new SieveCustomSortMethods(),
new SieveCustomFilterMethods())};
}
[Theory]
[MemberData(nameof(GetProcessors))]
public void MapperWorks(ISieveProcessor processor)
{ {
var model = new SieveModel var model = new SieveModel
{ {
Filters = "shortname@=A", Filters = "shortname@=A",
}; };
var result = processor.Apply(model, _posts); var result = _processor.Apply(model, _posts);
Assert.Equal("A", result.First().ThisHasNoAttributeButIsAccessible); Assert.Equal("A", result.First().ThisHasNoAttributeButIsAccessible);
Assert.True(result.Count() == 1); Assert.True(result.Count() == 1);
} }
[Theory] [Fact]
[MemberData(nameof(GetProcessors))] public void MapperSortOnlyWorks()
public void MapperSortOnlyWorks(ISieveProcessor processor)
{ {
var model = new SieveModel var model = new SieveModel
{ {
@ -91,9 +69,9 @@ namespace SieveUnitTests
Sorts = "OnlySortableViaFluentApi" Sorts = "OnlySortableViaFluentApi"
}; };
var result = processor.Apply(model, _posts, applyFiltering: false, applyPagination: false); var result = _processor.Apply(model, _posts, applyFiltering: false, applyPagination: false);
Assert.Throws<SieveMethodNotFoundException>(() => processor.Apply(model, _posts)); Assert.Throws<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
Assert.Equal(3, result.First().Id); Assert.Equal(3, result.First().Id);

View File

@ -1,26 +0,0 @@
using Microsoft.Extensions.Options;
using Sieve.Models;
using Sieve.Services;
using SieveUnitTests.Abstractions.Entity;
using SieveUnitTests.Entities;
namespace SieveUnitTests.Services
{
public class ModularConfigurationSieveProcessor : SieveProcessor
{
public ModularConfigurationSieveProcessor(
IOptions<SieveOptions> options,
ISieveCustomSortMethods customSortMethods,
ISieveCustomFilterMethods customFilterMethods)
: base(options, customSortMethods, customFilterMethods)
{
}
protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper)
{
return mapper
.ApplyConfiguration<SieveConfigurationForPost>()
.ApplyConfiguration<SieveConfigurationForIPost>();
}
}
}

View File

@ -1,20 +0,0 @@
using Microsoft.Extensions.Options;
using Sieve.Models;
using Sieve.Services;
namespace SieveUnitTests.Services
{
public class ModularConfigurationWithScanSieveProcessor : SieveProcessor
{
public ModularConfigurationWithScanSieveProcessor(
IOptions<SieveOptions> options,
ISieveCustomSortMethods customSortMethods,
ISieveCustomFilterMethods customFilterMethods)
: base(options, customSortMethods, customFilterMethods)
{
}
protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper) =>
mapper.ApplyConfigurationsFromAssembly(typeof(ModularConfigurationWithScanSieveProcessor).Assembly);
}
}

View File

@ -31,7 +31,7 @@ namespace SieveUnitTests
{ {
Id = 1, Id = 1,
DateCreated = DateTimeOffset.UtcNow, DateCreated = DateTimeOffset.UtcNow,
Text = "null is here twice in the text ending by null", Text = "null is here in the text",
Author = "Cat", Author = "Cat",
}, },
new Comment new Comment
@ -136,21 +136,6 @@ namespace SieveUnitTests
Assert.Equal(new[] {1}, result.Select(p => p.Id)); Assert.Equal(new[] {1}, result.Select(p => p.Id));
} }
[Theory]
[InlineData("Text_-=null")]
[InlineData("Text_-=*null")]
[InlineData("Text_-=*NULL")]
[InlineData("Text_-=*NulL")]
[InlineData("Text_-=*null|text")]
public void Filter_EndsWith_NullString(string filter)
{
var model = new SieveModel { Filters = filter };
var result = _processor.Apply(model, _comments);
Assert.Equal(new[] { 1 }, result.Select(p => p.Id));
}
[Theory] [Theory]
[InlineData("Text!@=null")] [InlineData("Text!@=null")]
[InlineData("Text!@=*null")] [InlineData("Text!@=*null")]
@ -179,19 +164,5 @@ namespace SieveUnitTests
Assert.Equal(new[] {0, 2}, result.Select(p => p.Id)); Assert.Equal(new[] {0, 2}, result.Select(p => p.Id));
} }
[Theory]
[InlineData("Text!_-=null")]
[InlineData("Text!_-=*null")]
[InlineData("Text!_-=*NULL")]
[InlineData("Text!_-=*NulL")]
public void Filter_DoesNotEndsWith_NullString(string filter)
{
var model = new SieveModel { Filters = filter };
var result = _processor.Apply(model, _comments);
Assert.Equal(new[] { 0, 2 }, result.Select(p => p.Id));
}
} }
} }