mirror of
https://github.com/Biarity/Sieve.git
synced 2024-11-23 22:12:48 +01:00
Merge pull request #126 from Biarity/sieve-3-based-on-pagr-fork
Merge changes from Pagr fork and prepare CI / CD
This commit is contained in:
commit
d513b108ba
31
.github/workflows/ci.yml
vendored
Normal file
31
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# <auto-generated>
|
||||
#
|
||||
# This code was generated.
|
||||
#
|
||||
# - To turn off auto-generation set:
|
||||
#
|
||||
# [GitHubActions (AutoGenerate = false)]
|
||||
#
|
||||
# - To trigger manual generation invoke:
|
||||
#
|
||||
# nuke --generate-configuration GitHubActions_ci --host GitHubActions
|
||||
#
|
||||
# </auto-generated>
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
ubuntu-latest:
|
||||
name: ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Run './build.cmd Ci'
|
||||
run: ./build.cmd Ci
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -261,3 +261,9 @@ paket-files/
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Nuke output
|
||||
/output
|
||||
|
||||
# Sample database
|
||||
Sieve.Sample/Sieve.db
|
||||
|
108
.nuke/build.schema.json
Normal file
108
.nuke/build.schema.json
Normal file
@ -0,0 +1,108 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Build Schema",
|
||||
"$ref": "#/definitions/build",
|
||||
"definitions": {
|
||||
"build": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Configuration": {
|
||||
"type": "string",
|
||||
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
||||
"enum": [
|
||||
"Debug",
|
||||
"Release"
|
||||
]
|
||||
},
|
||||
"Continue": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates to continue a previously failed build attempt"
|
||||
},
|
||||
"Help": {
|
||||
"type": "boolean",
|
||||
"description": "Shows the help text for this build assembly"
|
||||
},
|
||||
"Host": {
|
||||
"type": "string",
|
||||
"description": "Host for execution. Default is 'automatic'",
|
||||
"enum": [
|
||||
"AppVeyor",
|
||||
"AzurePipelines",
|
||||
"Bamboo",
|
||||
"Bitrise",
|
||||
"GitHubActions",
|
||||
"GitLab",
|
||||
"Jenkins",
|
||||
"SpaceAutomation",
|
||||
"TeamCity",
|
||||
"Terminal",
|
||||
"TravisCI"
|
||||
]
|
||||
},
|
||||
"NoLogo": {
|
||||
"type": "boolean",
|
||||
"description": "Disables displaying the NUKE logo"
|
||||
},
|
||||
"Plan": {
|
||||
"type": "boolean",
|
||||
"description": "Shows the execution plan (HTML)"
|
||||
},
|
||||
"Profile": {
|
||||
"type": "array",
|
||||
"description": "Defines the profiles to load",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"Root": {
|
||||
"type": "string",
|
||||
"description": "Root directory during build execution"
|
||||
},
|
||||
"Skip": {
|
||||
"type": "array",
|
||||
"description": "List of targets to be skipped. Empty list skips all dependencies",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Ci",
|
||||
"Clean",
|
||||
"Compile",
|
||||
"Package",
|
||||
"Restore",
|
||||
"Test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Solution": {
|
||||
"type": "string",
|
||||
"description": "Path to a solution file that is automatically loaded"
|
||||
},
|
||||
"Target": {
|
||||
"type": "array",
|
||||
"description": "List of targets to be invoked. Default is '{default_target}'",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Ci",
|
||||
"Clean",
|
||||
"Compile",
|
||||
"Package",
|
||||
"Restore",
|
||||
"Test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Verbosity": {
|
||||
"type": "string",
|
||||
"description": "Logging verbosity during build execution. Default is 'Normal'",
|
||||
"enum": [
|
||||
"Minimal",
|
||||
"Normal",
|
||||
"Quiet",
|
||||
"Verbose"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4
.nuke/parameters.json
Normal file
4
.nuke/parameters.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "./build.schema.json",
|
||||
"Solution": "Sieve.sln"
|
||||
}
|
1
GitVersion.yml
Normal file
1
GitVersion.yml
Normal file
@ -0,0 +1 @@
|
||||
next-version: 3.0
|
190
LICENSE
190
LICENSE
@ -1,192 +1,4 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2018 Biarity, 2021 a-patel and Kevin Sommer
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -2,10 +2,10 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sieve.Models;
|
||||
using Sieve.Sample.Entities;
|
||||
using Sieve.Services;
|
||||
using SieveTests.Entities;
|
||||
|
||||
namespace SieveTests.Controllers
|
||||
namespace Sieve.Sample.Controllers
|
||||
{
|
||||
[Route("api/[controller]/[action]")]
|
||||
public class PostsController : Controller
|
@ -1,6 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace SieveTests.Entities
|
||||
namespace Sieve.Sample.Entities
|
||||
{
|
||||
public class ApplicationDbContext : DbContext
|
||||
{
|
@ -2,14 +2,14 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Sieve.Attributes;
|
||||
|
||||
namespace SieveTests.Entities
|
||||
namespace Sieve.Sample.Entities
|
||||
{
|
||||
public class Post
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[Sieve(CanFilter = true, CanSort = true)]
|
||||
public string Title { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 8);
|
||||
public string Title { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty)[..8];
|
||||
|
||||
[Sieve(CanFilter = true, CanSort = true)]
|
||||
public int LikeCount { get; set; } = new Random().Next(0, 1000);
|
52
Sieve.Sample/Migrations/20210513114647_Initial.Designer.cs
generated
Normal file
52
Sieve.Sample/Migrations/20210513114647_Initial.Designer.cs
generated
Normal file
@ -0,0 +1,52 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Sieve.Sample.Entities;
|
||||
|
||||
namespace Sieve.Sample.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20210513114647_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "3.1.14");
|
||||
|
||||
modelBuilder.Entity("Sieve.Sample.Entities.Post", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("CategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CommentCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("DateCreated")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateLastViewed")
|
||||
.HasColumnType("datetime");
|
||||
|
||||
b.Property<int>("LikeCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Posts");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace SieveTests.Migrations
|
||||
namespace Sieve.Sample.Migrations
|
||||
{
|
||||
public partial class Init : Migration
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
@ -13,14 +12,18 @@ namespace SieveTests.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(nullable: false)
|
||||
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Title = table.Column<string>(nullable: true),
|
||||
LikeCount = table.Column<int>(nullable: false),
|
||||
CommentCount = table.Column<int>(nullable: false),
|
||||
DateCreated = table.Column<DateTimeOffset>(nullable: false),
|
||||
LikeCount = table.Column<int>(nullable: false),
|
||||
Title = table.Column<string>(nullable: true),
|
||||
DateLastViewed = table.Column<DateTime>(type: "datetime", nullable: false),
|
||||
CategoryId = table.Column<int>(nullable: true)
|
||||
},
|
||||
constraints: table => table.PrimaryKey("PK_Posts", x => x.Id));
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Posts", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
50
Sieve.Sample/Migrations/ApplicationDbContextModelSnapshot.cs
Normal file
50
Sieve.Sample/Migrations/ApplicationDbContextModelSnapshot.cs
Normal file
@ -0,0 +1,50 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Sieve.Sample.Entities;
|
||||
|
||||
namespace Sieve.Sample.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "3.1.14");
|
||||
|
||||
modelBuilder.Entity("Sieve.Sample.Entities.Post", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("CategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CommentCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("DateCreated")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateLastViewed")
|
||||
.HasColumnType("datetime");
|
||||
|
||||
b.Property<int>("LikeCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Posts");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace SieveTests
|
||||
namespace Sieve.Sample
|
||||
{
|
||||
public static class Program
|
||||
{
|
@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Sieve.Models;
|
||||
using Sieve.Sample.Entities;
|
||||
using Sieve.Services;
|
||||
using SieveTests.Entities;
|
||||
|
||||
namespace SieveTests.Services
|
||||
namespace Sieve.Sample.Services
|
||||
{
|
||||
public class ApplicationSieveProcessor : SieveProcessor
|
||||
{
|
@ -1,8 +1,8 @@
|
||||
using System.Linq;
|
||||
using Sieve.Sample.Entities;
|
||||
using Sieve.Services;
|
||||
using SieveTests.Entities;
|
||||
|
||||
namespace SieveTests.Services
|
||||
namespace Sieve.Sample.Services
|
||||
{
|
||||
public class SieveCustomFilterMethods : ISieveCustomFilterMethods
|
||||
{
|
@ -1,8 +1,8 @@
|
||||
using System.Linq;
|
||||
using Sieve.Sample.Entities;
|
||||
using Sieve.Services;
|
||||
using SieveTests.Entities;
|
||||
|
||||
namespace SieveTests.Services
|
||||
namespace Sieve.Sample.Services
|
||||
{
|
||||
public class SieveCustomSortMethods : ISieveCustomSortMethods
|
||||
{
|
20
Sieve.Sample/Sieve.Sample.csproj
Normal file
20
Sieve.Sample/Sieve.Sample.csproj
Normal file
@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.14">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.14" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.14" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SieveUnitTests\SieveUnitTests.csproj" />
|
||||
<ProjectReference Include="..\Sieve\Sieve.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
60
Sieve.Sample/Startup.cs
Normal file
60
Sieve.Sample/Startup.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Sieve.Models;
|
||||
using Sieve.Sample.Entities;
|
||||
using Sieve.Sample.Services;
|
||||
using Sieve.Services;
|
||||
|
||||
namespace Sieve.Sample
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMvc(opts =>
|
||||
{
|
||||
opts.EnableEndpointRouting = false;
|
||||
});
|
||||
|
||||
services.AddDbContext<ApplicationDbContext>(options =>
|
||||
options.UseSqlite("Data Source=.\\sieve.db"));
|
||||
|
||||
services.Configure<SieveOptions>(Configuration.GetSection("Sieve"));
|
||||
|
||||
services.AddScoped<ISieveCustomSortMethods, SieveCustomSortMethods>();
|
||||
services.AddScoped<ISieveCustomFilterMethods, SieveCustomFilterMethods>();
|
||||
services.AddScoped<ISieveProcessor, ApplicationSieveProcessor>();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
PrepareDatabase(app);
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseMvc();
|
||||
}
|
||||
|
||||
private static void PrepareDatabase(IApplicationBuilder app)
|
||||
{
|
||||
using var scope = app.ApplicationServices.CreateScope();
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
dbContext.Database.EnsureDeleted();
|
||||
dbContext.Database.Migrate();
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ VisualStudioVersion = 15.0.27130.2027
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sieve", "Sieve\Sieve.csproj", "{B32B8B33-94B0-40E3-8FE5-D54602222717}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SieveTests", "SieveTests\SieveTests.csproj", "{8043D264-42A0-4275-97A1-46400C02E37E}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sieve.Sample", "Sieve.Sample\Sieve.Sample.csproj", "{8043D264-42A0-4275-97A1-46400C02E37E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SieveUnitTests", "SieveUnitTests\SieveUnitTests.csproj", "{21C3082D-F40E-457F-BE2E-AA099E19E199}"
|
||||
EndProject
|
||||
@ -15,12 +15,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{C8829BE9-BC1E-4C67-ACEA-EC5DD3633EE2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C8829BE9-BC1E-4C67-ACEA-EC5DD3633EE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C8829BE9-BC1E-4C67-ACEA-EC5DD3633EE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B32B8B33-94B0-40E3-8FE5-D54602222717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B32B8B33-94B0-40E3-8FE5-D54602222717}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B32B8B33-94B0-40E3-8FE5-D54602222717}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
@ -13,8 +14,16 @@ namespace Sieve.Models
|
||||
where TFilterTerm : IFilterTerm, new()
|
||||
where TSortTerm : ISortTerm, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Pattern used to split filters and sorts by comma.
|
||||
/// </summary>
|
||||
private const string EscapedCommaPattern = @"(?<!($|[^\\])(\\\\)*?\\),\s*";
|
||||
|
||||
/// <summary>
|
||||
/// Escaped comma e.g. used in filter filter string.
|
||||
/// </summary>
|
||||
private const string EscapedComma = @"\,";
|
||||
|
||||
[DataMember]
|
||||
public string Filters { get; set; }
|
||||
|
||||
@ -34,15 +43,20 @@ namespace Sieve.Models
|
||||
var value = new List<TFilterTerm>();
|
||||
foreach (var filter in Regex.Split(Filters, EscapedCommaPattern))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filter)) continue;
|
||||
if (string.IsNullOrWhiteSpace(filter))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var filterValue = filter.Replace(EscapedComma, ",");
|
||||
|
||||
if (filter.StartsWith("("))
|
||||
{
|
||||
var filterOpAndVal = filter.Substring(filter.LastIndexOf(")") + 1);
|
||||
var subfilters = filter.Replace(filterOpAndVal, "").Replace("(", "").Replace(")", "");
|
||||
var filterOpAndVal = filterValue[(filterValue.LastIndexOf(")", StringComparison.Ordinal) + 1)..];
|
||||
var subFilters = filterValue.Replace(filterOpAndVal, "").Replace("(", "").Replace(")", "");
|
||||
var filterTerm = new TFilterTerm
|
||||
{
|
||||
Filter = subfilters + filterOpAndVal
|
||||
Filter = subFilters + filterOpAndVal
|
||||
};
|
||||
value.Add(filterTerm);
|
||||
}
|
||||
@ -50,7 +64,7 @@ namespace Sieve.Models
|
||||
{
|
||||
var filterTerm = new TFilterTerm
|
||||
{
|
||||
Filter = filter
|
||||
Filter = filterValue
|
||||
};
|
||||
value.Add(filterTerm);
|
||||
}
|
||||
@ -65,29 +79,28 @@ namespace Sieve.Models
|
||||
|
||||
public List<TSortTerm> GetSortsParsed()
|
||||
{
|
||||
if (Sorts != null)
|
||||
if (Sorts == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = new List<TSortTerm>();
|
||||
foreach (var sort in Regex.Split(Sorts, EscapedCommaPattern))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sort)) continue;
|
||||
|
||||
var sortTerm = new TSortTerm()
|
||||
var sortTerm = new TSortTerm
|
||||
{
|
||||
Sort = sort
|
||||
};
|
||||
if (!value.Any(s => s.Name == sortTerm.Name))
|
||||
|
||||
if (value.All(s => s.Name != sortTerm.Name))
|
||||
{
|
||||
value.Add(sortTerm);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,40 +14,50 @@ namespace Sieve.Services
|
||||
{
|
||||
public class SieveProcessor : SieveProcessor<SieveModel, FilterTerm, SortTerm>, ISieveProcessor
|
||||
{
|
||||
public SieveProcessor(IOptions<SieveOptions> options) : base(options)
|
||||
public SieveProcessor(IOptions<SieveOptions> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods) : base(options, customSortMethods)
|
||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods)
|
||||
: base(options, customSortMethods)
|
||||
{
|
||||
}
|
||||
|
||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomFilterMethods customFilterMethods) : base(options, customFilterMethods)
|
||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomFilterMethods customFilterMethods)
|
||||
: base(options, customFilterMethods)
|
||||
{
|
||||
}
|
||||
|
||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods, ISieveCustomFilterMethods customFilterMethods) : base(options, customSortMethods, customFilterMethods)
|
||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods,
|
||||
ISieveCustomFilterMethods customFilterMethods) : base(options, customSortMethods, customFilterMethods)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class SieveProcessor<TFilterTerm, TSortTerm> : SieveProcessor<SieveModel<TFilterTerm, TSortTerm>, TFilterTerm, TSortTerm>, ISieveProcessor<TFilterTerm, TSortTerm>
|
||||
public class SieveProcessor<TFilterTerm, TSortTerm> :
|
||||
SieveProcessor<SieveModel<TFilterTerm, TSortTerm>, TFilterTerm, TSortTerm>, ISieveProcessor<TFilterTerm, TSortTerm>
|
||||
where TFilterTerm : IFilterTerm, new()
|
||||
where TSortTerm : ISortTerm, new()
|
||||
{
|
||||
public SieveProcessor(IOptions<SieveOptions> options) : base(options)
|
||||
public SieveProcessor(IOptions<SieveOptions> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods) : base(options, customSortMethods)
|
||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods)
|
||||
: base(options, customSortMethods)
|
||||
{
|
||||
}
|
||||
|
||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomFilterMethods customFilterMethods) : base(options, customFilterMethods)
|
||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomFilterMethods customFilterMethods)
|
||||
: base(options, customFilterMethods)
|
||||
{
|
||||
}
|
||||
|
||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods, ISieveCustomFilterMethods customFilterMethods) : base(options, customSortMethods, customFilterMethods)
|
||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods,
|
||||
ISieveCustomFilterMethods customFilterMethods)
|
||||
: base(options, customSortMethods, customFilterMethods)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -57,17 +67,17 @@ namespace Sieve.Services
|
||||
where TFilterTerm : IFilterTerm, new()
|
||||
where TSortTerm : ISortTerm, new()
|
||||
{
|
||||
private const string nullFilterValue = "null";
|
||||
private const string NullFilterValue = "null";
|
||||
private readonly IOptions<SieveOptions> _options;
|
||||
private readonly ISieveCustomSortMethods _customSortMethods;
|
||||
private readonly ISieveCustomFilterMethods _customFilterMethods;
|
||||
private readonly SievePropertyMapper mapper = new SievePropertyMapper();
|
||||
private readonly SievePropertyMapper _mapper = new SievePropertyMapper();
|
||||
|
||||
public SieveProcessor(IOptions<SieveOptions> options,
|
||||
ISieveCustomSortMethods customSortMethods,
|
||||
ISieveCustomFilterMethods customFilterMethods)
|
||||
{
|
||||
mapper = MapProperties(mapper);
|
||||
_mapper = MapProperties(_mapper);
|
||||
_options = options;
|
||||
_customSortMethods = customSortMethods;
|
||||
_customFilterMethods = customFilterMethods;
|
||||
@ -76,7 +86,7 @@ namespace Sieve.Services
|
||||
public SieveProcessor(IOptions<SieveOptions> options,
|
||||
ISieveCustomSortMethods customSortMethods)
|
||||
{
|
||||
mapper = MapProperties(mapper);
|
||||
_mapper = MapProperties(_mapper);
|
||||
_options = options;
|
||||
_customSortMethods = customSortMethods;
|
||||
}
|
||||
@ -84,14 +94,14 @@ namespace Sieve.Services
|
||||
public SieveProcessor(IOptions<SieveOptions> options,
|
||||
ISieveCustomFilterMethods customFilterMethods)
|
||||
{
|
||||
mapper = MapProperties(mapper);
|
||||
_mapper = MapProperties(_mapper);
|
||||
_options = options;
|
||||
_customFilterMethods = customFilterMethods;
|
||||
}
|
||||
|
||||
public SieveProcessor(IOptions<SieveOptions> options)
|
||||
{
|
||||
mapper = MapProperties(mapper);
|
||||
_mapper = MapProperties(_mapper);
|
||||
_options = options;
|
||||
}
|
||||
|
||||
@ -106,12 +116,8 @@ namespace Sieve.Services
|
||||
/// <param name="applySorting">Should the data be sorted? Defaults to true.</param>
|
||||
/// <param name="applyPagination">Should the data be paginated? Defaults to true.</param>
|
||||
/// <returns>Returns a transformed version of `source`</returns>
|
||||
public IQueryable<TEntity> Apply<TEntity>(
|
||||
TSieveModel model,
|
||||
IQueryable<TEntity> source,
|
||||
object[] dataForCustomMethods = null,
|
||||
bool applyFiltering = true,
|
||||
bool applySorting = true,
|
||||
public IQueryable<TEntity> Apply<TEntity>(TSieveModel model, IQueryable<TEntity> source,
|
||||
object[] dataForCustomMethods = null, bool applyFiltering = true, bool applySorting = true,
|
||||
bool applyPagination = true)
|
||||
{
|
||||
var result = source;
|
||||
@ -123,19 +129,16 @@ namespace Sieve.Services
|
||||
|
||||
try
|
||||
{
|
||||
// Filter
|
||||
if (applyFiltering)
|
||||
{
|
||||
result = ApplyFiltering(model, result, dataForCustomMethods);
|
||||
}
|
||||
|
||||
// Sort
|
||||
if (applySorting)
|
||||
{
|
||||
result = ApplySorting(model, result, dataForCustomMethods);
|
||||
}
|
||||
|
||||
// Paginate
|
||||
if (applyPagination)
|
||||
{
|
||||
result = ApplyPagination(model, result);
|
||||
@ -145,8 +148,11 @@ namespace Sieve.Services
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (_options.Value.ThrowExceptions)
|
||||
if (!_options.Value.ThrowExceptions)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (ex is SieveException)
|
||||
{
|
||||
throw;
|
||||
@ -154,16 +160,9 @@ namespace Sieve.Services
|
||||
|
||||
throw new SieveException(ex.Message, ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IQueryable<TEntity> ApplyFiltering<TEntity>(
|
||||
TSieveModel model,
|
||||
IQueryable<TEntity> result,
|
||||
private IQueryable<TEntity> ApplyFiltering<TEntity>(TSieveModel model, IQueryable<TEntity> result,
|
||||
object[] dataForCustomMethods = null)
|
||||
{
|
||||
if (model?.GetFiltersParsed() == null)
|
||||
@ -181,25 +180,20 @@ namespace Sieve.Services
|
||||
var (fullPropertyName, property) = GetSieveProperty<TEntity>(false, true, filterTermName);
|
||||
if (property != null)
|
||||
{
|
||||
Expression propertyValue = parameter;
|
||||
Expression nullCheck = null;
|
||||
var names = fullPropertyName.Split('.');
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
if (filterTerm.Values == null)
|
||||
{
|
||||
propertyValue = Expression.PropertyOrField(propertyValue, names[i]);
|
||||
|
||||
if (i != names.Length - 1 && propertyValue.Type.IsNullable())
|
||||
{
|
||||
nullCheck = GenerateFilterNullCheckExpression(propertyValue, nullCheck);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (filterTerm.Values == null) continue;
|
||||
|
||||
var converter = TypeDescriptor.GetConverter(property.PropertyType);
|
||||
foreach (var filterTermValue in filterTerm.Values)
|
||||
{
|
||||
var isFilterTermValueNull = filterTermValue.ToLower() == nullFilterValue;
|
||||
var (propertyValue, nullCheck) =
|
||||
GetPropertyValueAndNullCheckExpression(parameter, fullPropertyName);
|
||||
|
||||
var isFilterTermValueNull =
|
||||
IsFilterTermValueNull(propertyValue, filterTerm, filterTermValue);
|
||||
|
||||
var filterValue = isFilterTermValueNull
|
||||
? Expression.Constant(null, property.PropertyType)
|
||||
: ConvertStringValueToConstantExpression(filterTermValue, property, converter);
|
||||
@ -222,60 +216,97 @@ namespace Sieve.Services
|
||||
expression = Expression.Not(expression);
|
||||
}
|
||||
|
||||
var filterValueNullCheck = !isFilterTermValueNull && propertyValue.Type.IsNullable()
|
||||
? GenerateFilterNullCheckExpression(propertyValue, nullCheck)
|
||||
: nullCheck;
|
||||
|
||||
var filterValueNullCheck = GetFilterValueNullCheck(parameter, fullPropertyName, isFilterTermValueNull);
|
||||
if (filterValueNullCheck != null)
|
||||
{
|
||||
expression = Expression.AndAlso(filterValueNullCheck, expression);
|
||||
}
|
||||
|
||||
if (innerExpression == null)
|
||||
{
|
||||
innerExpression = expression;
|
||||
}
|
||||
else
|
||||
{
|
||||
innerExpression = Expression.OrElse(innerExpression, expression);
|
||||
}
|
||||
innerExpression = innerExpression == null
|
||||
? expression
|
||||
: Expression.OrElse(innerExpression, expression);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ApplyCustomMethod(result, filterTermName, _customFilterMethods,
|
||||
new object[] {
|
||||
result,
|
||||
filterTerm.Operator,
|
||||
filterTerm.Values
|
||||
}, dataForCustomMethods);
|
||||
new object[] {result, filterTerm.Operator, filterTerm.Values}, dataForCustomMethods);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (outerExpression == null)
|
||||
{
|
||||
outerExpression = innerExpression;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (innerExpression == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
outerExpression = Expression.AndAlso(outerExpression, innerExpression);
|
||||
}
|
||||
|
||||
return outerExpression == null
|
||||
? result
|
||||
: result.Where(Expression.Lambda<Func<TEntity, bool>>(outerExpression, parameter));
|
||||
}
|
||||
|
||||
private static Expression GenerateFilterNullCheckExpression(Expression propertyValue, Expression nullCheckExpression)
|
||||
private static Expression GetFilterValueNullCheck(Expression parameter, string fullPropertyName,
|
||||
bool isFilterTermValueNull)
|
||||
{
|
||||
var (propertyValue, nullCheck) = GetPropertyValueAndNullCheckExpression(parameter, fullPropertyName);
|
||||
|
||||
if (!isFilterTermValueNull && propertyValue.Type.IsNullable())
|
||||
{
|
||||
return GenerateFilterNullCheckExpression(propertyValue, nullCheck);
|
||||
}
|
||||
|
||||
return nullCheck;
|
||||
}
|
||||
|
||||
private static bool IsFilterTermValueNull(Expression propertyValue, TFilterTerm filterTerm,
|
||||
string filterTermValue)
|
||||
{
|
||||
var isNotString = propertyValue.Type != typeof(string);
|
||||
|
||||
var isValidStringNullOperation = filterTerm.OperatorParsed == FilterOperator.Equals ||
|
||||
filterTerm.OperatorParsed == FilterOperator.NotEquals;
|
||||
|
||||
return filterTermValue.ToLower() == NullFilterValue && (isNotString || isValidStringNullOperation);
|
||||
}
|
||||
|
||||
private static (Expression propertyValue, Expression nullCheck) GetPropertyValueAndNullCheckExpression(
|
||||
Expression parameter, string fullPropertyName)
|
||||
{
|
||||
var propertyValue = parameter;
|
||||
Expression nullCheck = null;
|
||||
var names = fullPropertyName.Split('.');
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
{
|
||||
propertyValue = Expression.PropertyOrField(propertyValue, names[i]);
|
||||
|
||||
if (i != names.Length - 1 && propertyValue.Type.IsNullable())
|
||||
{
|
||||
nullCheck = GenerateFilterNullCheckExpression(propertyValue, nullCheck);
|
||||
}
|
||||
}
|
||||
|
||||
return (propertyValue, nullCheck);
|
||||
}
|
||||
|
||||
private static Expression GenerateFilterNullCheckExpression(Expression propertyValue,
|
||||
Expression nullCheckExpression)
|
||||
{
|
||||
return nullCheckExpression == null
|
||||
? Expression.NotEqual(propertyValue, Expression.Default(propertyValue.Type))
|
||||
: Expression.AndAlso(nullCheckExpression, Expression.NotEqual(propertyValue, Expression.Default(propertyValue.Type)));
|
||||
: Expression.AndAlso(nullCheckExpression,
|
||||
Expression.NotEqual(propertyValue, Expression.Default(propertyValue.Type)));
|
||||
}
|
||||
|
||||
private Expression ConvertStringValueToConstantExpression(string value, PropertyInfo property, TypeConverter converter)
|
||||
private static Expression ConvertStringValueToConstantExpression(string value, PropertyInfo property,
|
||||
TypeConverter converter)
|
||||
{
|
||||
dynamic constantVal = converter.CanConvertFrom(typeof(string))
|
||||
? converter.ConvertFrom(value)
|
||||
@ -286,47 +317,34 @@ namespace Sieve.Services
|
||||
|
||||
private static Expression GetExpression(TFilterTerm filterTerm, dynamic filterValue, dynamic propertyValue)
|
||||
{
|
||||
switch (filterTerm.OperatorParsed)
|
||||
return filterTerm.OperatorParsed switch
|
||||
{
|
||||
case FilterOperator.Equals:
|
||||
return Expression.Equal(propertyValue, filterValue);
|
||||
case FilterOperator.NotEquals:
|
||||
return Expression.NotEqual(propertyValue, filterValue);
|
||||
case FilterOperator.GreaterThan:
|
||||
return Expression.GreaterThan(propertyValue, filterValue);
|
||||
case FilterOperator.LessThan:
|
||||
return Expression.LessThan(propertyValue, filterValue);
|
||||
case FilterOperator.GreaterThanOrEqualTo:
|
||||
return Expression.GreaterThanOrEqual(propertyValue, filterValue);
|
||||
case FilterOperator.LessThanOrEqualTo:
|
||||
return Expression.LessThanOrEqual(propertyValue, filterValue);
|
||||
case FilterOperator.Contains:
|
||||
return Expression.Call(propertyValue,
|
||||
typeof(string).GetMethods()
|
||||
.First(m => m.Name == "Contains" && m.GetParameters().Length == 1),
|
||||
filterValue);
|
||||
case FilterOperator.StartsWith:
|
||||
return Expression.Call(propertyValue,
|
||||
typeof(string).GetMethods()
|
||||
.First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1),
|
||||
filterValue);
|
||||
default:
|
||||
return Expression.Equal(propertyValue, filterValue);
|
||||
}
|
||||
FilterOperator.Equals => Expression.Equal(propertyValue, filterValue),
|
||||
FilterOperator.NotEquals => Expression.NotEqual(propertyValue, filterValue),
|
||||
FilterOperator.GreaterThan => Expression.GreaterThan(propertyValue, filterValue),
|
||||
FilterOperator.LessThan => Expression.LessThan(propertyValue, filterValue),
|
||||
FilterOperator.GreaterThanOrEqualTo => Expression.GreaterThanOrEqual(propertyValue, filterValue),
|
||||
FilterOperator.LessThanOrEqualTo => Expression.LessThanOrEqual(propertyValue, filterValue),
|
||||
FilterOperator.Contains => Expression.Call(propertyValue,
|
||||
typeof(string).GetMethods().First(m => m.Name == "Contains" && m.GetParameters().Length == 1),
|
||||
filterValue),
|
||||
FilterOperator.StartsWith => Expression.Call(propertyValue,
|
||||
typeof(string).GetMethods().First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1),
|
||||
filterValue),
|
||||
_ => Expression.Equal(propertyValue, filterValue)
|
||||
};
|
||||
}
|
||||
|
||||
// Workaround to ensure that the filter value gets passed as a parameter in generated SQL from EF Core
|
||||
// See https://github.com/aspnet/EntityFrameworkCore/issues/3361
|
||||
// Expression.Constant passed the target type to allow Nullable comparison
|
||||
// See http://bradwilson.typepad.com/blog/2008/07/creating-nullab.html
|
||||
private Expression GetClosureOverConstant<T>(T constant, Type targetType)
|
||||
private static Expression GetClosureOverConstant<T>(T constant, Type targetType)
|
||||
{
|
||||
return Expression.Constant(constant, targetType);
|
||||
}
|
||||
|
||||
private IQueryable<TEntity> ApplySorting<TEntity>(
|
||||
TSieveModel model,
|
||||
IQueryable<TEntity> result,
|
||||
private IQueryable<TEntity> ApplySorting<TEntity>(TSieveModel model, IQueryable<TEntity> result,
|
||||
object[] dataForCustomMethods = null)
|
||||
{
|
||||
if (model?.GetSortsParsed() == null)
|
||||
@ -346,32 +364,28 @@ namespace Sieve.Services
|
||||
else
|
||||
{
|
||||
result = ApplyCustomMethod(result, sortTerm.Name, _customSortMethods,
|
||||
new object[]
|
||||
{
|
||||
result,
|
||||
useThenBy,
|
||||
sortTerm.Descending
|
||||
}, dataForCustomMethods);
|
||||
new object[] {result, useThenBy, sortTerm.Descending}, dataForCustomMethods);
|
||||
}
|
||||
|
||||
useThenBy = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IQueryable<TEntity> ApplyPagination<TEntity>(
|
||||
TSieveModel model,
|
||||
IQueryable<TEntity> result)
|
||||
private IQueryable<TEntity> ApplyPagination<TEntity>(TSieveModel model, IQueryable<TEntity> result)
|
||||
{
|
||||
var page = model?.Page ?? 1;
|
||||
var pageSize = model?.PageSize ?? _options.Value.DefaultPageSize;
|
||||
var maxPageSize = _options.Value.MaxPageSize > 0 ? _options.Value.MaxPageSize : pageSize;
|
||||
|
||||
if (pageSize > 0)
|
||||
if (pageSize <= 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = result.Skip((page - 1) * pageSize);
|
||||
result = result.Take(Math.Min(pageSize, maxPageSize));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -381,41 +395,40 @@ namespace Sieve.Services
|
||||
return mapper;
|
||||
}
|
||||
|
||||
private (string, PropertyInfo) GetSieveProperty<TEntity>(
|
||||
bool canSortRequired,
|
||||
bool canFilterRequired,
|
||||
private (string, PropertyInfo) GetSieveProperty<TEntity>(bool canSortRequired, bool canFilterRequired,
|
||||
string name)
|
||||
{
|
||||
var property = mapper.FindProperty<TEntity>(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive);
|
||||
if (property.Item1 == null)
|
||||
var property = _mapper.FindProperty<TEntity>(canSortRequired, canFilterRequired, name,
|
||||
_options.Value.CaseSensitive);
|
||||
if (property.Item1 != null)
|
||||
{
|
||||
var prop = FindPropertyBySieveAttribute<TEntity>(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive);
|
||||
return property;
|
||||
}
|
||||
|
||||
var prop = FindPropertyBySieveAttribute<TEntity>(canSortRequired, canFilterRequired, name,
|
||||
_options.Value.CaseSensitive);
|
||||
return (prop?.Name, prop);
|
||||
}
|
||||
return property;
|
||||
|
||||
private static PropertyInfo FindPropertyBySieveAttribute<TEntity>(bool canSortRequired, bool canFilterRequired,
|
||||
string name, bool isCaseSensitive)
|
||||
{
|
||||
return Array.Find(typeof(TEntity).GetProperties(),
|
||||
p => p.GetCustomAttribute(typeof(SieveAttribute)) is SieveAttribute SieveAttribute
|
||||
&& (!canSortRequired || SieveAttribute.CanSort)
|
||||
&& (!canFilterRequired || SieveAttribute.CanFilter)
|
||||
&& (SieveAttribute.Name ?? p.Name).Equals(name,
|
||||
isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private PropertyInfo FindPropertyBySieveAttribute<TEntity>(
|
||||
bool canSortRequired,
|
||||
bool canFilterRequired,
|
||||
string name,
|
||||
bool isCaseSensitive)
|
||||
{
|
||||
return Array.Find(typeof(TEntity).GetProperties(), p =>
|
||||
{
|
||||
return p.GetCustomAttribute(typeof(SieveAttribute)) is SieveAttribute sieveAttribute
|
||||
&& (!canSortRequired || sieveAttribute.CanSort)
|
||||
&& (!canFilterRequired || sieveAttribute.CanFilter)
|
||||
&& (sieveAttribute.Name ?? p.Name).Equals(name, isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
}
|
||||
|
||||
private IQueryable<TEntity> ApplyCustomMethod<TEntity>(IQueryable<TEntity> result, string name, object parent, object[] parameters, object[] optionalParameters = null)
|
||||
private IQueryable<TEntity> ApplyCustomMethod<TEntity>(IQueryable<TEntity> result, string name, object parent,
|
||||
object[] parameters, object[] optionalParameters = null)
|
||||
{
|
||||
var customMethod = parent?.GetType()
|
||||
.GetMethodExt(name,
|
||||
_options.Value.CaseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance,
|
||||
_options.Value.CaseSensitive
|
||||
? BindingFlags.Default
|
||||
: BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance,
|
||||
typeof(IQueryable<TEntity>));
|
||||
|
||||
|
||||
@ -424,7 +437,9 @@ namespace Sieve.Services
|
||||
// Find generic methods `public IQueryable<T> Filter<T>(IQueryable<T> source, ...)`
|
||||
var genericCustomMethod = parent?.GetType()
|
||||
.GetMethodExt(name,
|
||||
_options.Value.CaseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance,
|
||||
_options.Value.CaseSensitive
|
||||
? BindingFlags.Default
|
||||
: BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance,
|
||||
typeof(IQueryable<>));
|
||||
|
||||
if (genericCustomMethod != null &&
|
||||
@ -433,7 +448,8 @@ namespace Sieve.Services
|
||||
{
|
||||
var genericBaseType = genericCustomMethod.ReturnType.GenericTypeArguments[0];
|
||||
var constraints = genericBaseType.GetGenericParameterConstraints();
|
||||
if (constraints == null || constraints.Length == 0 || constraints.All((t) => t.IsAssignableFrom(typeof(TEntity))))
|
||||
if (constraints == null || constraints.Length == 0 ||
|
||||
constraints.All((t) => t.IsAssignableFrom(typeof(TEntity))))
|
||||
{
|
||||
customMethod = genericCustomMethod.MakeGenericMethod(typeof(TEntity));
|
||||
}
|
||||
@ -462,25 +478,24 @@ namespace Sieve.Services
|
||||
}
|
||||
else
|
||||
{
|
||||
var incompatibleCustomMethods = parent?
|
||||
var incompatibleCustomMethods =
|
||||
parent?
|
||||
.GetType()
|
||||
.GetMethods
|
||||
(
|
||||
_options.Value.CaseSensitive
|
||||
.GetMethods(_options.Value.CaseSensitive
|
||||
? BindingFlags.Default
|
||||
: BindingFlags.IgnoreCase | BindingFlags.Public |
|
||||
BindingFlags.Instance
|
||||
)
|
||||
: BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(method => string.Equals(method.Name, name,
|
||||
_options.Value.CaseSensitive
|
||||
? StringComparison.InvariantCulture
|
||||
: StringComparison.InvariantCultureIgnoreCase))
|
||||
.ToList()
|
||||
??
|
||||
new List<MethodInfo>();
|
||||
?? new List<MethodInfo>();
|
||||
|
||||
if (incompatibleCustomMethods.Any())
|
||||
if (!incompatibleCustomMethods.Any())
|
||||
{
|
||||
throw new SieveMethodNotFoundException(name, $"{name} not found.");
|
||||
}
|
||||
|
||||
var incompatibles =
|
||||
from incompatibleCustomMethod in incompatibleCustomMethods
|
||||
let expected = typeof(IQueryable<TEntity>)
|
||||
@ -492,11 +507,6 @@ namespace Sieve.Services
|
||||
|
||||
throw new SieveIncompatibleMethodException(aggregate.Message, aggregate);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SieveMethodNotFoundException(name, $"{name} not found.");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1,22 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Version>2.3.3</Version>
|
||||
<Description>Sieve is a simple, clean, and extensible framework for .NET Core that adds sorting, filtering, and pagination functionality out of the box. Most common use case would be for serving ASP.NET Core GET queries. Documentation available on GitHub: https://github.com/Biarity/Sieve/
|
||||
</Description>
|
||||
<Copyright>Copyright 2018</Copyright>
|
||||
<PackageLicenseUrl>https://github.com/Biarity/Sieve/blob/master/LICENSE</PackageLicenseUrl>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Description>Sieve is a simple, clean, and extensible framework for .NET Core that adds sorting, filtering, and pagination functionality out of the box. Most common use case would be for serving ASP.NET Core GET queries. Documentation available on GitHub: https://github.com/Biarity/Sieve/</Description>
|
||||
<Authors>Biarity;Ashish Patel;Kevin Sommer</Authors>
|
||||
|
||||
<PackageTags>Filter;Sort;Page;Paging;</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/Biarity/Sieve</PackageProjectUrl>
|
||||
<PackageIconUrl>https://emojipedia-us.s3.amazonaws.com/thumbs/240/twitter/120/alembic_2697.png</PackageIconUrl>
|
||||
<RepositoryUrl></RepositoryUrl>
|
||||
<PackageReleaseNotes>Only Skip when pageSize > 0 (#63)
|
||||
Added support for generic filter and sort methods (#60)
|
||||
Don't process when filterTerm.Values is null (#59)
|
||||
</PackageReleaseNotes>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<Authors>Biarity</Authors>
|
||||
|
||||
<RepositoryUrl>https://github.com/Biarity/Sieve</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
|
||||
<!-- Declare that the Repository URL can be published to NuSpec -->
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<!-- Embed source files that are not tracked by the source control manager to the PDB -->
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<!-- Include PDB in the built .nupkg -->
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
BIN
Sieve/nuget.exe
BIN
Sieve/nuget.exe
Binary file not shown.
@ -1,44 +0,0 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||
using SieveTests.Entities;
|
||||
using System;
|
||||
|
||||
namespace SieveTests.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20180127005347_Init")]
|
||||
partial class Init
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.1-rtm-125")
|
||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
modelBuilder.Entity("SieveTests.Entities.Post", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("CommentCount");
|
||||
|
||||
b.Property<DateTimeOffset>("DateCreated");
|
||||
|
||||
b.Property<int>("LikeCount");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Posts");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||
using SieveTests.Entities;
|
||||
using System;
|
||||
|
||||
namespace SieveTests.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20180522013323_AddDateLastViewedColumn")]
|
||||
partial class AddDateLastViewedColumn
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.1-rtm-125")
|
||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
modelBuilder.Entity("SieveTests.Entities.Post", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("CommentCount");
|
||||
|
||||
b.Property<DateTimeOffset>("DateCreated");
|
||||
|
||||
b.Property<DateTime>("DateLastViewed")
|
||||
.HasColumnType("datetime");
|
||||
|
||||
b.Property<int>("LikeCount");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Posts");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace SieveTests.Migrations
|
||||
{
|
||||
public partial class AddDateLastViewedColumn : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "DateLastViewed",
|
||||
table: "Posts",
|
||||
type: "datetime",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DateLastViewed",
|
||||
table: "Posts");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||
using SieveTests.Entities;
|
||||
using System;
|
||||
|
||||
namespace SieveTests.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.1-rtm-125")
|
||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||
|
||||
modelBuilder.Entity("SieveTests.Entities.Post", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("CommentCount");
|
||||
|
||||
b.Property<DateTimeOffset>("DateCreated");
|
||||
|
||||
b.Property<DateTime>("DateLastViewed")
|
||||
.HasColumnType("datetime");
|
||||
|
||||
b.Property<int>("LikeCount");
|
||||
|
||||
b.Property<int?>("CategoryId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Posts");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Migrations\20180127005211_Itit.cs" />
|
||||
<Compile Remove="Migrations\20180127005211_Itit.Designer.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SieveUnitTests\SieveUnitTests.csproj" />
|
||||
<ProjectReference Include="..\Sieve\Sieve.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Sieve.Models;
|
||||
using Sieve.Services;
|
||||
using SieveTests.Entities;
|
||||
using SieveTests.Services;
|
||||
|
||||
namespace SieveTests
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMvc();
|
||||
|
||||
services.AddDbContext<ApplicationDbContext>(options =>
|
||||
options.UseSqlServer(Configuration.GetConnectionString("TestSqlServer")));
|
||||
|
||||
services.Configure<SieveOptions>(Configuration.GetSection("Sieve"));
|
||||
|
||||
services.AddScoped<ISieveCustomSortMethods, SieveCustomSortMethods>();
|
||||
services.AddScoped<ISieveCustomFilterMethods, SieveCustomFilterMethods>();
|
||||
services.AddScoped<ISieveProcessor, ApplicationSieveProcessor>();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
// TIME MEASUREMENT
|
||||
var times = new List<long>();
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
await next.Invoke();
|
||||
sw.Stop();
|
||||
times.Add(sw.ElapsedMilliseconds);
|
||||
var text = $"AVG: {(int)times.Average()}ms; AT {sw.ElapsedMilliseconds}; COUNT: {times.Count()}";
|
||||
Console.WriteLine(text);
|
||||
await context.Response.WriteAsync($"<!-- {text} -->");
|
||||
});
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
@ -7,5 +7,8 @@ namespace SieveUnitTests.Entities
|
||||
{
|
||||
[Sieve(CanFilter = true)]
|
||||
public string Text { get; set; }
|
||||
|
||||
[Sieve(CanFilter = true)]
|
||||
public string Author { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sieve.Exceptions;
|
||||
using Sieve.Models;
|
||||
using Sieve.Services;
|
||||
using SieveUnitTests.Abstractions.Entity;
|
||||
using SieveUnitTests.Entities;
|
||||
using SieveUnitTests.Services;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace SieveUnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class General
|
||||
{
|
||||
private readonly ITestOutputHelper _testOutputHelper;
|
||||
private readonly SieveProcessor _processor;
|
||||
private readonly IQueryable<Post> _posts;
|
||||
private readonly IQueryable<Comment> _comments;
|
||||
|
||||
public General()
|
||||
public General(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_testOutputHelper = testOutputHelper;
|
||||
_processor = new ApplicationSieveProcessor(new SieveOptionsAccessor(),
|
||||
new SieveCustomSortMethods(),
|
||||
new SieveCustomFilterMethods());
|
||||
|
||||
_posts = new List<Post>
|
||||
{
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 0,
|
||||
Title = "A",
|
||||
LikeCount = 100,
|
||||
@ -35,7 +37,8 @@ namespace SieveUnitTests
|
||||
TopComment = new Comment { Id = 0, Text = "A1" },
|
||||
FeaturedComment = new Comment { Id = 4, Text = "A2" }
|
||||
},
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 1,
|
||||
Title = "B",
|
||||
LikeCount = 50,
|
||||
@ -44,7 +47,8 @@ namespace SieveUnitTests
|
||||
TopComment = new Comment { Id = 3, Text = "B1" },
|
||||
FeaturedComment = new Comment { Id = 5, Text = "B2" }
|
||||
},
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 2,
|
||||
Title = "C",
|
||||
LikeCount = 0,
|
||||
@ -52,7 +56,8 @@ namespace SieveUnitTests
|
||||
TopComment = new Comment { Id = 2, Text = "C1" },
|
||||
FeaturedComment = new Comment { Id = 6, Text = "C2" }
|
||||
},
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 3,
|
||||
Title = "D",
|
||||
LikeCount = 3,
|
||||
@ -65,281 +70,301 @@ namespace SieveUnitTests
|
||||
|
||||
_comments = new List<Comment>
|
||||
{
|
||||
new Comment() {
|
||||
new Comment
|
||||
{
|
||||
Id = 0,
|
||||
DateCreated = DateTimeOffset.UtcNow.AddDays(-20),
|
||||
Text = "This is an old comment."
|
||||
},
|
||||
new Comment() {
|
||||
new Comment
|
||||
{
|
||||
Id = 1,
|
||||
DateCreated = DateTimeOffset.UtcNow.AddDays(-1),
|
||||
Text = "This is a fairly new comment."
|
||||
},
|
||||
new Comment() {
|
||||
new Comment
|
||||
{
|
||||
Id = 2,
|
||||
DateCreated = DateTimeOffset.UtcNow,
|
||||
Text = "This is a brand new comment. (Text in braces)"
|
||||
Text = "This is a brand new comment. (Text in braces, comma separated)"
|
||||
},
|
||||
}.AsQueryable();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void ContainsCanBeCaseInsensitive()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Title@=*a"
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.AreEqual(result.First().Id, 0);
|
||||
Assert.IsTrue(result.Count() == 1);
|
||||
Assert.Equal(0, result.First().Id);
|
||||
Assert.True(result.Count() == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void NotEqualsCanBeCaseInsensitive()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Title!=*a"
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.AreEqual(result.First().Id, 1);
|
||||
Assert.IsTrue(result.Count() == 3);
|
||||
Assert.Equal(1, result.First().Id);
|
||||
Assert.True(result.Count() == 3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void ContainsIsCaseSensitive()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Title@=a",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Count() == 0);
|
||||
Assert.True(!result.Any());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void NotContainsWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Title!@=D",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Count() == 3);
|
||||
Assert.True(result.Count() == 3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CanFilterBools()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "IsDraft==false"
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Count() == 2);
|
||||
Assert.True(result.Count() == 2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CanSortBools()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Sorts = "-IsDraft"
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.AreEqual(result.First().Id, 0);
|
||||
Assert.Equal(0, result.First().Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CanFilterNullableInts()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "CategoryId==1"
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Count() == 2);
|
||||
Assert.True(result.Count() == 2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Theory]
|
||||
[InlineData(@"Text@=*\,")]
|
||||
[InlineData(@"Text@=*\, ")]
|
||||
[InlineData(@"Text@=*braces\,")]
|
||||
[InlineData(@"Text@=*braces\, comma")]
|
||||
public void CanFilterWithEscapedComma(string filter)
|
||||
{
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = filter
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _comments);
|
||||
|
||||
Assert.True(result.Count() == 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualsDoesntFailWithNonStringTypes()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "LikeCount==50",
|
||||
};
|
||||
|
||||
Console.WriteLine(model.GetFiltersParsed()[0].Values);
|
||||
Console.WriteLine(model.GetFiltersParsed()[0].Operator);
|
||||
Console.WriteLine(model.GetFiltersParsed()[0].OperatorParsed);
|
||||
_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.AreEqual(result.First().Id, 1);
|
||||
Assert.IsTrue(result.Count() == 1);
|
||||
Assert.Equal(1, result.First().Id);
|
||||
Assert.True(result.Count() == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomFiltersWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Isnew",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsFalse(result.Any(p => p.Id == 0));
|
||||
Assert.IsTrue(result.Count() == 3);
|
||||
Assert.False(result.Any(p => p.Id == 0));
|
||||
Assert.True(result.Count() == 3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomGenericFiltersWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Latest",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _comments);
|
||||
|
||||
Assert.IsFalse(result.Any(p => p.Id == 0));
|
||||
Assert.IsTrue(result.Count() == 2);
|
||||
Assert.False(result.Any(p => p.Id == 0));
|
||||
Assert.True(result.Count() == 2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomFiltersWithOperatorsWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "HasInTitle==A",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Any(p => p.Id == 0));
|
||||
Assert.IsTrue(result.Count() == 1);
|
||||
Assert.True(result.Any(p => p.Id == 0));
|
||||
Assert.True(result.Count() == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomFiltersMixedWithUsualWork1()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Isnew,CategoryId==2",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Any(p => p.Id == 3));
|
||||
Assert.IsTrue(result.Count() == 1);
|
||||
Assert.True(result.Any(p => p.Id == 3));
|
||||
Assert.True(result.Count() == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomFiltersMixedWithUsualWork2()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "CategoryId==2,Isnew",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Any(p => p.Id == 3));
|
||||
Assert.IsTrue(result.Count() == 1);
|
||||
Assert.True(result.Any(p => p.Id == 3));
|
||||
Assert.True(result.Count() == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomFiltersOnDifferentSourcesCanShareName()
|
||||
{
|
||||
var postModel = new SieveModel()
|
||||
var postModel = new SieveModel
|
||||
{
|
||||
Filters = "CategoryId==2,Isnew",
|
||||
};
|
||||
|
||||
var postResult = _processor.Apply(postModel, _posts);
|
||||
|
||||
Assert.IsTrue(postResult.Any(p => p.Id == 3));
|
||||
Assert.AreEqual(1, postResult.Count());
|
||||
Assert.True(postResult.Any(p => p.Id == 3));
|
||||
Assert.Equal(1, postResult.Count());
|
||||
|
||||
var commentModel = new SieveModel()
|
||||
var commentModel = new SieveModel
|
||||
{
|
||||
Filters = "Isnew",
|
||||
};
|
||||
|
||||
var commentResult = _processor.Apply(commentModel, _comments);
|
||||
|
||||
Assert.IsTrue(commentResult.Any(c => c.Id == 2));
|
||||
Assert.AreEqual(2, commentResult.Count());
|
||||
Assert.True(commentResult.Any(c => c.Id == 2));
|
||||
Assert.Equal(2, commentResult.Count());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomSortsWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Sorts = "Popularity",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsFalse(result.First().Id == 0);
|
||||
Assert.False(result.First().Id == 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomGenericSortsWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Sorts = "Oldest",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Last().Id == 0);
|
||||
Assert.True(result.Last().Id == 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void MethodNotFoundExceptionWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "does not exist",
|
||||
};
|
||||
|
||||
Assert.ThrowsException<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
||||
Assert.Throws<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void IncompatibleMethodExceptionsWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "TestComment",
|
||||
};
|
||||
|
||||
Assert.ThrowsException<SieveIncompatibleMethodException>(() => _processor.Apply(model, _posts));
|
||||
Assert.Throws<SieveIncompatibleMethodException>(() => _processor.Apply(model, _posts));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void OrNameFilteringWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "(Title|LikeCount)==3",
|
||||
};
|
||||
@ -348,17 +373,17 @@ namespace SieveUnitTests
|
||||
var entry = result.FirstOrDefault();
|
||||
var resultCount = result.Count();
|
||||
|
||||
Assert.IsNotNull(entry);
|
||||
Assert.AreEqual(1, resultCount);
|
||||
Assert.AreEqual(3, entry.Id);
|
||||
Assert.NotNull(entry);
|
||||
Assert.Equal(1, resultCount);
|
||||
Assert.Equal(3, entry.Id);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("CategoryId==1,(CategoryId|LikeCount)==50")]
|
||||
[DataRow("(CategoryId|LikeCount)==50,CategoryId==1")]
|
||||
[Theory]
|
||||
[InlineData("CategoryId==1,(CategoryId|LikeCount)==50")]
|
||||
[InlineData("(CategoryId|LikeCount)==50,CategoryId==1")]
|
||||
public void CombinedAndOrFilterIndependentOfOrder(string filter)
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = filter,
|
||||
};
|
||||
@ -367,14 +392,14 @@ namespace SieveUnitTests
|
||||
var entry = result.FirstOrDefault();
|
||||
var resultCount = result.Count();
|
||||
|
||||
Assert.IsNotNull(entry);
|
||||
Assert.AreEqual(1, resultCount);
|
||||
Assert.NotNull(entry);
|
||||
Assert.Equal(1, resultCount);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CombinedAndOrWithSpaceFilteringWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Title==D, (Title|LikeCount)==3",
|
||||
};
|
||||
@ -383,97 +408,98 @@ namespace SieveUnitTests
|
||||
var entry = result.FirstOrDefault();
|
||||
var resultCount = result.Count();
|
||||
|
||||
Assert.IsNotNull(entry);
|
||||
Assert.AreEqual(1, resultCount);
|
||||
Assert.AreEqual(3, entry.Id);
|
||||
Assert.NotNull(entry);
|
||||
Assert.Equal(1, resultCount);
|
||||
Assert.Equal(3, entry.Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void OrValueFilteringWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Title==C|D",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
Assert.AreEqual(2, result.Count());
|
||||
Assert.IsTrue(result.Any(p => p.Id == 2));
|
||||
Assert.IsTrue(result.Any(p => p.Id == 3));
|
||||
Assert.Equal(2, result.Count());
|
||||
Assert.True(result.Any(p => p.Id == 2));
|
||||
Assert.True(result.Any(p => p.Id == 3));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void OrValueFilteringWorks2()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Text@=(|)",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _comments);
|
||||
Assert.AreEqual(1, result.Count());
|
||||
Assert.AreEqual(2, result.FirstOrDefault().Id);
|
||||
Assert.Equal(1, result.Count());
|
||||
Assert.Equal(2, result.FirstOrDefault()?.Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void NestedFilteringWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "TopComment.Text!@=A",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
Assert.AreEqual(3, result.Count());
|
||||
Assert.Equal(3, result.Count());
|
||||
var posts = result.ToList();
|
||||
Assert.IsTrue(posts[0].TopComment.Text.Contains("B"));
|
||||
Assert.IsTrue(posts[1].TopComment.Text.Contains("C"));
|
||||
Assert.IsTrue(posts[2].TopComment.Text.Contains("D"));
|
||||
Assert.Contains("B", posts[0].TopComment.Text);
|
||||
Assert.Contains("C", posts[1].TopComment.Text);
|
||||
Assert.Contains("D", posts[2].TopComment.Text);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void NestedSortingWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Sorts = "TopComment.Id",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
Assert.AreEqual(4, result.Count());
|
||||
Assert.Equal(4, result.Count());
|
||||
var posts = result.ToList();
|
||||
Assert.AreEqual(posts[0].Id, 0);
|
||||
Assert.AreEqual(posts[1].Id, 3);
|
||||
Assert.AreEqual(posts[2].Id, 2);
|
||||
Assert.AreEqual(posts[3].Id, 1);
|
||||
Assert.Equal(0, posts[0].Id);
|
||||
Assert.Equal(3, posts[1].Id);
|
||||
Assert.Equal(2, posts[2].Id);
|
||||
Assert.Equal(1, posts[3].Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void NestedFilteringWithIdenticTypesWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "(topc|featc)@=*2",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
Assert.AreEqual(4, result.Count());
|
||||
Assert.Equal(4, result.Count());
|
||||
|
||||
model = new SieveModel()
|
||||
model = new SieveModel
|
||||
{
|
||||
Filters = "(topc|featc)@=*B",
|
||||
};
|
||||
|
||||
result = _processor.Apply(model, _posts);
|
||||
Assert.AreEqual(1, result.Count());
|
||||
Assert.Equal(1, result.Count());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void FilteringNullsWorks()
|
||||
{
|
||||
var posts = new List<Post>
|
||||
{
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 1,
|
||||
Title = null,
|
||||
LikeCount = 0,
|
||||
@ -484,21 +510,22 @@ namespace SieveUnitTests
|
||||
},
|
||||
}.AsQueryable();
|
||||
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "FeaturedComment.Text!@=Some value",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, posts);
|
||||
Assert.AreEqual(0, result.Count());
|
||||
Assert.Equal(0, result.Count());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void SortingNullsWorks()
|
||||
{
|
||||
var posts = new List<Post>
|
||||
{
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 1,
|
||||
Title = null,
|
||||
LikeCount = 0,
|
||||
@ -507,7 +534,8 @@ namespace SieveUnitTests
|
||||
TopComment = new Comment { Id = 1 },
|
||||
FeaturedComment = null
|
||||
},
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 2,
|
||||
Title = null,
|
||||
LikeCount = 0,
|
||||
@ -518,24 +546,25 @@ namespace SieveUnitTests
|
||||
},
|
||||
}.AsQueryable();
|
||||
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Sorts = "TopComment.Id",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, posts);
|
||||
Assert.AreEqual(2, result.Count());
|
||||
Assert.Equal(2, result.Count());
|
||||
var sortedPosts = result.ToList();
|
||||
Assert.AreEqual(sortedPosts[0].Id, 2);
|
||||
Assert.AreEqual(sortedPosts[1].Id, 1);
|
||||
Assert.Equal(2, sortedPosts[0].Id);
|
||||
Assert.Equal(1, sortedPosts[1].Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void FilteringOnNullWorks()
|
||||
{
|
||||
var posts = new List<Post>
|
||||
{
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 1,
|
||||
Title = null,
|
||||
LikeCount = 0,
|
||||
@ -544,7 +573,8 @@ namespace SieveUnitTests
|
||||
TopComment = null,
|
||||
FeaturedComment = null
|
||||
},
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 2,
|
||||
Title = null,
|
||||
LikeCount = 0,
|
||||
@ -555,33 +585,33 @@ namespace SieveUnitTests
|
||||
},
|
||||
}.AsQueryable();
|
||||
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "FeaturedComment.Text==null",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, posts);
|
||||
Assert.AreEqual(1, result.Count());
|
||||
Assert.Equal(1, result.Count());
|
||||
var filteredPosts = result.ToList();
|
||||
Assert.AreEqual(filteredPosts[0].Id, 2);
|
||||
Assert.Equal(2, filteredPosts[0].Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void BaseDefinedPropertyMappingSortingWorks_WithCustomName()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Sorts = "-CreateDate"
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
Assert.AreEqual(4, result.Count());
|
||||
Assert.Equal(4, result.Count());
|
||||
|
||||
var posts = result.ToList();
|
||||
Assert.AreEqual(posts[0].Id, 3);
|
||||
Assert.AreEqual(posts[1].Id, 2);
|
||||
Assert.AreEqual(posts[2].Id, 1);
|
||||
Assert.AreEqual(posts[3].Id, 0);
|
||||
Assert.Equal(3,posts[0].Id);
|
||||
Assert.Equal(2,posts[1].Id);
|
||||
Assert.Equal(1,posts[2].Id);
|
||||
Assert.Equal(0,posts[3].Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sieve.Exceptions;
|
||||
using Sieve.Models;
|
||||
using Sieve.Services;
|
||||
using SieveUnitTests.Abstractions.Entity;
|
||||
using SieveUnitTests.Entities;
|
||||
using SieveUnitTests.Services;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace SieveUnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class GeneralWithInterfaces
|
||||
{
|
||||
private readonly ITestOutputHelper _testOutputHelper;
|
||||
private readonly SieveProcessor _processor;
|
||||
private readonly IQueryable<IPost> _posts;
|
||||
private readonly IQueryable<Comment> _comments;
|
||||
|
||||
public GeneralWithInterfaces()
|
||||
public GeneralWithInterfaces(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_testOutputHelper = testOutputHelper;
|
||||
_processor = new ApplicationSieveProcessor(new SieveOptionsAccessor(),
|
||||
new SieveCustomSortMethods(),
|
||||
new SieveCustomFilterMethods());
|
||||
|
||||
_posts = new List<IPost>
|
||||
{
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 0,
|
||||
Title = "A",
|
||||
LikeCount = 100,
|
||||
@ -36,7 +38,8 @@ namespace SieveUnitTests
|
||||
TopComment = new Comment { Id = 0, Text = "A1" },
|
||||
FeaturedComment = new Comment { Id = 4, Text = "A2" }
|
||||
},
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 1,
|
||||
Title = "B",
|
||||
LikeCount = 50,
|
||||
@ -45,7 +48,8 @@ namespace SieveUnitTests
|
||||
TopComment = new Comment { Id = 3, Text = "B1" },
|
||||
FeaturedComment = new Comment { Id = 5, Text = "B2" }
|
||||
},
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 2,
|
||||
Title = "C",
|
||||
LikeCount = 0,
|
||||
@ -53,7 +57,8 @@ namespace SieveUnitTests
|
||||
TopComment = new Comment { Id = 2, Text = "C1" },
|
||||
FeaturedComment = new Comment { Id = 6, Text = "C2" }
|
||||
},
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 3,
|
||||
Title = "D",
|
||||
LikeCount = 3,
|
||||
@ -66,17 +71,20 @@ namespace SieveUnitTests
|
||||
|
||||
_comments = new List<Comment>
|
||||
{
|
||||
new Comment() {
|
||||
new Comment
|
||||
{
|
||||
Id = 0,
|
||||
DateCreated = DateTimeOffset.UtcNow.AddDays(-20),
|
||||
Text = "This is an old comment."
|
||||
},
|
||||
new Comment() {
|
||||
new Comment
|
||||
{
|
||||
Id = 1,
|
||||
DateCreated = DateTimeOffset.UtcNow.AddDays(-1),
|
||||
Text = "This is a fairly new comment."
|
||||
},
|
||||
new Comment() {
|
||||
new Comment
|
||||
{
|
||||
Id = 2,
|
||||
DateCreated = DateTimeOffset.UtcNow,
|
||||
Text = "This is a brand new comment. (Text in braces)"
|
||||
@ -84,263 +92,263 @@ namespace SieveUnitTests
|
||||
}.AsQueryable();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void ContainsCanBeCaseInsensitive()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Title@=*a"
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.AreEqual(result.First().Id, 0);
|
||||
Assert.IsTrue(result.Count() == 1);
|
||||
Assert.Equal(0, result.First().Id);
|
||||
Assert.True(result.Count() == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void NotEqualsCanBeCaseInsensitive()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Title!=*a"
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.AreEqual(result.First().Id, 1);
|
||||
Assert.IsTrue(result.Count() == 3);
|
||||
Assert.Equal(1, result.First().Id);
|
||||
Assert.True(result.Count() == 3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void ContainsIsCaseSensitive()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Title@=a",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Count() == 0);
|
||||
Assert.True(!result.Any());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void NotContainsWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Title!@=D",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Count() == 3);
|
||||
Assert.True(result.Count() == 3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CanFilterBools()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "IsDraft==false"
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Count() == 2);
|
||||
Assert.True(result.Count() == 2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CanSortBools()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Sorts = "-IsDraft"
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.AreEqual(result.First().Id, 0);
|
||||
Assert.Equal(0, result.First().Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CanFilterNullableInts()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "CategoryId==1"
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Count() == 2);
|
||||
Assert.True(result.Count() == 2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void EqualsDoesntFailWithNonStringTypes()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "LikeCount==50",
|
||||
};
|
||||
|
||||
Console.WriteLine(model.GetFiltersParsed()[0].Values);
|
||||
Console.WriteLine(model.GetFiltersParsed()[0].Operator);
|
||||
Console.WriteLine(model.GetFiltersParsed()[0].OperatorParsed);
|
||||
_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.AreEqual(result.First().Id, 1);
|
||||
Assert.IsTrue(result.Count() == 1);
|
||||
Assert.Equal(1, result.First().Id);
|
||||
Assert.True(result.Count() == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomFiltersWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Isnew",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsFalse(result.Any(p => p.Id == 0));
|
||||
Assert.IsTrue(result.Count() == 3);
|
||||
Assert.False(result.Any(p => p.Id == 0));
|
||||
Assert.True(result.Count() == 3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomGenericFiltersWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Latest",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _comments);
|
||||
|
||||
Assert.IsFalse(result.Any(p => p.Id == 0));
|
||||
Assert.IsTrue(result.Count() == 2);
|
||||
Assert.False(result.Any(p => p.Id == 0));
|
||||
Assert.True(result.Count() == 2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomFiltersWithOperatorsWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "HasInTitle==A",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Any(p => p.Id == 0));
|
||||
Assert.IsTrue(result.Count() == 1);
|
||||
Assert.True(result.Any(p => p.Id == 0));
|
||||
Assert.True(result.Count() == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomFiltersMixedWithUsualWork1()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Isnew,CategoryId==2",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Any(p => p.Id == 3));
|
||||
Assert.IsTrue(result.Count() == 1);
|
||||
Assert.True(result.Any(p => p.Id == 3));
|
||||
Assert.True(result.Count() == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomFiltersMixedWithUsualWork2()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "CategoryId==2,Isnew",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Any(p => p.Id == 3));
|
||||
Assert.IsTrue(result.Count() == 1);
|
||||
Assert.True(result.Any(p => p.Id == 3));
|
||||
Assert.True(result.Count() == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomFiltersOnDifferentSourcesCanShareName()
|
||||
{
|
||||
var postModel = new SieveModel()
|
||||
var postModel = new SieveModel
|
||||
{
|
||||
Filters = "CategoryId==2,Isnew",
|
||||
};
|
||||
|
||||
var postResult = _processor.Apply(postModel, _posts);
|
||||
|
||||
Assert.IsTrue(postResult.Any(p => p.Id == 3));
|
||||
Assert.AreEqual(1, postResult.Count());
|
||||
Assert.True(postResult.Any(p => p.Id == 3));
|
||||
Assert.Equal(1, postResult.Count());
|
||||
|
||||
var commentModel = new SieveModel()
|
||||
var commentModel = new SieveModel
|
||||
{
|
||||
Filters = "Isnew",
|
||||
};
|
||||
|
||||
var commentResult = _processor.Apply(commentModel, _comments);
|
||||
|
||||
Assert.IsTrue(commentResult.Any(c => c.Id == 2));
|
||||
Assert.AreEqual(2, commentResult.Count());
|
||||
Assert.True(commentResult.Any(c => c.Id == 2));
|
||||
Assert.Equal(2, commentResult.Count());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomSortsWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Sorts = "Popularity",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsFalse(result.First().Id == 0);
|
||||
Assert.False(result.First().Id == 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CustomGenericSortsWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Sorts = "Oldest",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.IsTrue(result.Last().Id == 0);
|
||||
Assert.True(result.Last().Id == 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void MethodNotFoundExceptionWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "does not exist",
|
||||
};
|
||||
|
||||
Assert.ThrowsException<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
||||
Assert.Throws<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void IncompatibleMethodExceptionsWork()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "TestComment",
|
||||
};
|
||||
|
||||
Assert.ThrowsException<SieveIncompatibleMethodException>(() => _processor.Apply(model, _posts));
|
||||
Assert.Throws<SieveIncompatibleMethodException>(() => _processor.Apply(model, _posts));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void OrNameFilteringWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "(Title|LikeCount)==3",
|
||||
};
|
||||
@ -349,17 +357,17 @@ namespace SieveUnitTests
|
||||
var entry = result.FirstOrDefault();
|
||||
var resultCount = result.Count();
|
||||
|
||||
Assert.IsNotNull(entry);
|
||||
Assert.AreEqual(1, resultCount);
|
||||
Assert.AreEqual(3, entry.Id);
|
||||
Assert.NotNull(entry);
|
||||
Assert.Equal(1, resultCount);
|
||||
Assert.Equal(3, entry.Id);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("CategoryId==1,(CategoryId|LikeCount)==50")]
|
||||
[DataRow("(CategoryId|LikeCount)==50,CategoryId==1")]
|
||||
[Theory]
|
||||
[InlineData("CategoryId==1,(CategoryId|LikeCount)==50")]
|
||||
[InlineData("(CategoryId|LikeCount)==50,CategoryId==1")]
|
||||
public void CombinedAndOrFilterIndependentOfOrder(string filter)
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = filter,
|
||||
};
|
||||
@ -368,14 +376,14 @@ namespace SieveUnitTests
|
||||
var entry = result.FirstOrDefault();
|
||||
var resultCount = result.Count();
|
||||
|
||||
Assert.IsNotNull(entry);
|
||||
Assert.AreEqual(1, resultCount);
|
||||
Assert.NotNull(entry);
|
||||
Assert.Equal(1, resultCount);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void CombinedAndOrWithSpaceFilteringWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Title==D, (Title|LikeCount)==3",
|
||||
};
|
||||
@ -384,97 +392,98 @@ namespace SieveUnitTests
|
||||
var entry = result.FirstOrDefault();
|
||||
var resultCount = result.Count();
|
||||
|
||||
Assert.IsNotNull(entry);
|
||||
Assert.AreEqual(1, resultCount);
|
||||
Assert.AreEqual(3, entry.Id);
|
||||
Assert.NotNull(entry);
|
||||
Assert.Equal(1, resultCount);
|
||||
Assert.Equal(3, entry.Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void OrValueFilteringWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Title==C|D",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
Assert.AreEqual(2, result.Count());
|
||||
Assert.IsTrue(result.Any(p => p.Id == 2));
|
||||
Assert.IsTrue(result.Any(p => p.Id == 3));
|
||||
Assert.Equal(2, result.Count());
|
||||
Assert.True(result.Any(p => p.Id == 2));
|
||||
Assert.True(result.Any(p => p.Id == 3));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void OrValueFilteringWorks2()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "Text@=(|)",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _comments);
|
||||
Assert.AreEqual(1, result.Count());
|
||||
Assert.AreEqual(2, result.FirstOrDefault().Id);
|
||||
Assert.Equal(1, result.Count());
|
||||
Assert.Equal(2, result.FirstOrDefault()?.Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void NestedFilteringWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "TopComment.Text!@=A",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
Assert.AreEqual(3, result.Count());
|
||||
Assert.Equal(3, result.Count());
|
||||
var posts = result.ToList();
|
||||
Assert.IsTrue(posts[0].TopComment.Text.Contains("B"));
|
||||
Assert.IsTrue(posts[1].TopComment.Text.Contains("C"));
|
||||
Assert.IsTrue(posts[2].TopComment.Text.Contains("D"));
|
||||
Assert.Contains("B", posts[0].TopComment.Text);
|
||||
Assert.Contains("C", posts[1].TopComment.Text);
|
||||
Assert.Contains("D", posts[2].TopComment.Text);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void NestedSortingWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Sorts = "TopComment.Id",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
Assert.AreEqual(4, result.Count());
|
||||
Assert.Equal(4, result.Count());
|
||||
var posts = result.ToList();
|
||||
Assert.AreEqual(posts[0].Id, 0);
|
||||
Assert.AreEqual(posts[1].Id, 3);
|
||||
Assert.AreEqual(posts[2].Id, 2);
|
||||
Assert.AreEqual(posts[3].Id, 1);
|
||||
Assert.Equal(0, posts[0].Id);
|
||||
Assert.Equal(3, posts[1].Id);
|
||||
Assert.Equal(2, posts[2].Id);
|
||||
Assert.Equal(1, posts[3].Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void NestedFilteringWithIdenticTypesWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "(topc|featc)@=*2",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
Assert.AreEqual(4, result.Count());
|
||||
Assert.Equal(4, result.Count());
|
||||
|
||||
model = new SieveModel()
|
||||
model = new SieveModel
|
||||
{
|
||||
Filters = "(topc|featc)@=*B",
|
||||
};
|
||||
|
||||
result = _processor.Apply(model, _posts);
|
||||
Assert.AreEqual(1, result.Count());
|
||||
Assert.Equal(1, result.Count());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void FilteringNullsWorks()
|
||||
{
|
||||
var posts = new List<Post>
|
||||
{
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 1,
|
||||
Title = null,
|
||||
LikeCount = 0,
|
||||
@ -485,21 +494,22 @@ namespace SieveUnitTests
|
||||
},
|
||||
}.AsQueryable();
|
||||
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "FeaturedComment.Text!@=Some value",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, posts);
|
||||
Assert.AreEqual(0, result.Count());
|
||||
Assert.Equal(0, result.Count());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void SortingNullsWorks()
|
||||
{
|
||||
var posts = new List<Post>
|
||||
{
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 1,
|
||||
Title = null,
|
||||
LikeCount = 0,
|
||||
@ -508,7 +518,8 @@ namespace SieveUnitTests
|
||||
TopComment = new Comment { Id = 1 },
|
||||
FeaturedComment = null
|
||||
},
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 2,
|
||||
Title = null,
|
||||
LikeCount = 0,
|
||||
@ -519,24 +530,25 @@ namespace SieveUnitTests
|
||||
},
|
||||
}.AsQueryable();
|
||||
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Sorts = "TopComment.Id",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, posts);
|
||||
Assert.AreEqual(2, result.Count());
|
||||
Assert.Equal(2, result.Count());
|
||||
var sortedPosts = result.ToList();
|
||||
Assert.AreEqual(sortedPosts[0].Id, 2);
|
||||
Assert.AreEqual(sortedPosts[1].Id, 1);
|
||||
Assert.Equal(2, sortedPosts[0].Id);
|
||||
Assert.Equal(1, sortedPosts[1].Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void FilteringOnNullWorks()
|
||||
{
|
||||
var posts = new List<Post>
|
||||
{
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 1,
|
||||
Title = null,
|
||||
LikeCount = 0,
|
||||
@ -545,7 +557,8 @@ namespace SieveUnitTests
|
||||
TopComment = null,
|
||||
FeaturedComment = null
|
||||
},
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 2,
|
||||
Title = null,
|
||||
LikeCount = 0,
|
||||
@ -556,33 +569,33 @@ namespace SieveUnitTests
|
||||
},
|
||||
}.AsQueryable();
|
||||
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "FeaturedComment.Text==null",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, posts);
|
||||
Assert.AreEqual(1, result.Count());
|
||||
Assert.Equal(1, result.Count());
|
||||
var filteredPosts = result.ToList();
|
||||
Assert.AreEqual(filteredPosts[0].Id, 2);
|
||||
Assert.Equal(2, filteredPosts[0].Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void BaseDefinedPropertyMappingSortingWorks_WithCustomName()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Sorts = "-CreateDate"
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
Assert.AreEqual(4, result.Count());
|
||||
Assert.Equal(4, result.Count());
|
||||
|
||||
var posts = result.ToList();
|
||||
Assert.AreEqual(posts[0].Id, 3);
|
||||
Assert.AreEqual(posts[1].Id, 2);
|
||||
Assert.AreEqual(posts[2].Id, 1);
|
||||
Assert.AreEqual(posts[3].Id, 0);
|
||||
Assert.Equal(3,posts[0].Id);
|
||||
Assert.Equal(2,posts[1].Id);
|
||||
Assert.Equal(1,posts[2].Id);
|
||||
Assert.Equal(0,posts[3].Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Sieve.Exceptions;
|
||||
using Sieve.Models;
|
||||
using SieveUnitTests.Entities;
|
||||
using SieveUnitTests.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace SieveUnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class Mapper
|
||||
{
|
||||
private readonly ApplicationSieveProcessor _processor;
|
||||
@ -22,19 +21,22 @@ namespace SieveUnitTests
|
||||
|
||||
_posts = new List<Post>
|
||||
{
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 1,
|
||||
ThisHasNoAttributeButIsAccessible = "A",
|
||||
ThisHasNoAttribute = "A",
|
||||
OnlySortableViaFluentApi = 100
|
||||
},
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 2,
|
||||
ThisHasNoAttributeButIsAccessible = "B",
|
||||
ThisHasNoAttribute = "B",
|
||||
OnlySortableViaFluentApi = 50
|
||||
},
|
||||
new Post() {
|
||||
new Post
|
||||
{
|
||||
Id = 3,
|
||||
ThisHasNoAttributeButIsAccessible = "C",
|
||||
ThisHasNoAttribute = "C",
|
||||
@ -43,25 +45,25 @@ namespace SieveUnitTests
|
||||
}.AsQueryable();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void MapperWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "shortname@=A",
|
||||
};
|
||||
|
||||
var result = _processor.Apply(model, _posts);
|
||||
|
||||
Assert.AreEqual(result.First().ThisHasNoAttributeButIsAccessible, "A");
|
||||
Assert.Equal("A", result.First().ThisHasNoAttributeButIsAccessible);
|
||||
|
||||
Assert.IsTrue(result.Count() == 1);
|
||||
Assert.True(result.Count() == 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void MapperSortOnlyWorks()
|
||||
{
|
||||
var model = new SieveModel()
|
||||
var model = new SieveModel
|
||||
{
|
||||
Filters = "OnlySortableViaFluentApi@=50",
|
||||
Sorts = "OnlySortableViaFluentApi"
|
||||
@ -69,17 +71,11 @@ namespace SieveUnitTests
|
||||
|
||||
var result = _processor.Apply(model, _posts, applyFiltering: false, applyPagination: false);
|
||||
|
||||
Assert.ThrowsException<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
||||
Assert.Throws<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
||||
|
||||
Assert.AreEqual(result.First().Id, 3);
|
||||
Assert.Equal(3, result.First().Id);
|
||||
|
||||
Assert.IsTrue(result.Count() == 3);
|
||||
Assert.True(result.Count() == 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//Sorts = "LikeCount",
|
||||
//Page = 1,
|
||||
//PageSize = 10
|
||||
//
|
@ -1,15 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
148
SieveUnitTests/StringFilterNullTests.cs
Normal file
148
SieveUnitTests/StringFilterNullTests.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Sieve.Models;
|
||||
using Sieve.Services;
|
||||
using SieveUnitTests.Entities;
|
||||
using SieveUnitTests.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace SieveUnitTests
|
||||
{
|
||||
public class StringFilterNullTests
|
||||
{
|
||||
private readonly IQueryable<Comment> _comments;
|
||||
private readonly SieveProcessor _processor;
|
||||
|
||||
public StringFilterNullTests()
|
||||
{
|
||||
_processor = new SieveProcessor(new SieveOptionsAccessor());
|
||||
|
||||
_comments = new List<Comment>
|
||||
{
|
||||
new Comment
|
||||
{
|
||||
Id = 0,
|
||||
DateCreated = DateTimeOffset.UtcNow,
|
||||
Text = "This text contains null somewhere in the middle of a string",
|
||||
Author = "Dog",
|
||||
},
|
||||
new Comment
|
||||
{
|
||||
Id = 1,
|
||||
DateCreated = DateTimeOffset.UtcNow,
|
||||
Text = "null is here in the text",
|
||||
Author = "Cat",
|
||||
},
|
||||
new Comment
|
||||
{
|
||||
Id = 2,
|
||||
DateCreated = DateTimeOffset.UtcNow,
|
||||
Text = "Regular comment without n*ll.",
|
||||
Author = "Mouse",
|
||||
},
|
||||
new Comment
|
||||
{
|
||||
Id = 100,
|
||||
DateCreated = DateTimeOffset.UtcNow,
|
||||
Text = null,
|
||||
Author = "null",
|
||||
},
|
||||
}.AsQueryable();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Filter_Equals_Null()
|
||||
{
|
||||
var model = new SieveModel {Filters = "Text==null"};
|
||||
|
||||
var result = _processor.Apply(model, _comments);
|
||||
|
||||
Assert.Equal(100, result.Single().Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Filter_NotEquals_Null()
|
||||
{
|
||||
var model = new SieveModel {Filters = "Text!=null"};
|
||||
|
||||
var result = _processor.Apply(model, _comments);
|
||||
|
||||
Assert.Equal(new[] {0, 1, 2}, 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_Contains_NullString(string filter)
|
||||
{
|
||||
var model = new SieveModel {Filters = filter};
|
||||
|
||||
var result = _processor.Apply(model, _comments);
|
||||
|
||||
Assert.Equal(new[] {0, 1}, result.Select(p => p.Id));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Text|Author==null", 100)]
|
||||
[InlineData("Text|Author@=null", 0, 1, 100)]
|
||||
[InlineData("Text|Author@=*null", 0, 1, 100)]
|
||||
[InlineData("Text|Author_=null", 1, 100)]
|
||||
[InlineData("Text|Author_=*null", 1, 100)]
|
||||
public void MultiFilter_Contains_NullString(string filter, params int[] expectedIds)
|
||||
{
|
||||
var model = new SieveModel {Filters = filter};
|
||||
|
||||
var result = _processor.Apply(model, _comments);
|
||||
|
||||
Assert.Equal(expectedIds, 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_StartsWith_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]
|
||||
[InlineData("Text!@=null")]
|
||||
[InlineData("Text!@=*null")]
|
||||
[InlineData("Text!@=*NULL")]
|
||||
[InlineData("Text!@=*NulL")]
|
||||
[InlineData("Text!@=*null|text")]
|
||||
public void Filter_DoesNotContain_NullString(string filter)
|
||||
{
|
||||
var model = new SieveModel {Filters = filter};
|
||||
|
||||
var result = _processor.Apply(model, _comments);
|
||||
|
||||
Assert.Equal(new[] {2}, result.Select(p => p.Id));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Text!_=null")]
|
||||
[InlineData("Text!_=*null")]
|
||||
[InlineData("Text!_=*NULL")]
|
||||
[InlineData("Text!_=*NulL")]
|
||||
public void Filter_DoesNotStartsWith_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));
|
||||
}
|
||||
}
|
||||
}
|
7
build.cmd
Executable file
7
build.cmd
Executable file
@ -0,0 +1,7 @@
|
||||
:; set -eo pipefail
|
||||
:; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||
:; ${SCRIPT_DIR}/build.sh "$@"
|
||||
:; exit $?
|
||||
|
||||
@ECHO OFF
|
||||
powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*
|
69
build.ps1
Normal file
69
build.ps1
Normal file
@ -0,0 +1,69 @@
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
|
||||
[string[]]$BuildArguments
|
||||
)
|
||||
|
||||
Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)"
|
||||
|
||||
Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 }
|
||||
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
|
||||
|
||||
###########################################################################
|
||||
# CONFIGURATION
|
||||
###########################################################################
|
||||
|
||||
$BuildProjectFile = "$PSScriptRoot\build\_build.csproj"
|
||||
$TempDirectory = "$PSScriptRoot\\.nuke\temp"
|
||||
|
||||
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
|
||||
$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
|
||||
$DotNetChannel = "Current"
|
||||
|
||||
$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
|
||||
$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
|
||||
$env:DOTNET_MULTILEVEL_LOOKUP = 0
|
||||
|
||||
###########################################################################
|
||||
# EXECUTION
|
||||
###########################################################################
|
||||
|
||||
function ExecSafe([scriptblock] $cmd) {
|
||||
& $cmd
|
||||
if ($LASTEXITCODE) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
# If dotnet CLI is installed globally and it matches requested version, use for execution
|
||||
if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
|
||||
$(dotnet --version) -and $LASTEXITCODE -eq 0) {
|
||||
$env:DOTNET_EXE = (Get-Command "dotnet").Path
|
||||
}
|
||||
else {
|
||||
# Download install script
|
||||
$DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
|
||||
New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
(New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
|
||||
|
||||
# If global.json exists, load expected version
|
||||
if (Test-Path $DotNetGlobalFile) {
|
||||
$DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
|
||||
if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
|
||||
$DotNetVersion = $DotNetGlobal.sdk.version
|
||||
}
|
||||
}
|
||||
|
||||
# Install by channel or version
|
||||
$DotNetDirectory = "$TempDirectory\dotnet-win"
|
||||
if (!(Test-Path variable:DotNetVersion)) {
|
||||
ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
|
||||
} else {
|
||||
ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
|
||||
}
|
||||
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
|
||||
}
|
||||
|
||||
Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"
|
||||
|
||||
ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
|
||||
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }
|
62
build.sh
Executable file
62
build.sh
Executable file
@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
bash --version 2>&1 | head -n 1
|
||||
|
||||
set -eo pipefail
|
||||
SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||
|
||||
###########################################################################
|
||||
# CONFIGURATION
|
||||
###########################################################################
|
||||
|
||||
BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj"
|
||||
TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
|
||||
|
||||
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
|
||||
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
|
||||
DOTNET_CHANNEL="Current"
|
||||
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
|
||||
export DOTNET_MULTILEVEL_LOOKUP=0
|
||||
|
||||
###########################################################################
|
||||
# EXECUTION
|
||||
###########################################################################
|
||||
|
||||
function FirstJsonValue {
|
||||
perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}"
|
||||
}
|
||||
|
||||
# If dotnet CLI is installed globally and it matches requested version, use for execution
|
||||
if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then
|
||||
export DOTNET_EXE="$(command -v dotnet)"
|
||||
else
|
||||
# Download install script
|
||||
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
|
||||
mkdir -p "$TEMP_DIRECTORY"
|
||||
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
|
||||
chmod +x "$DOTNET_INSTALL_FILE"
|
||||
|
||||
# If global.json exists, load expected version
|
||||
if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then
|
||||
DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")")
|
||||
if [[ "$DOTNET_VERSION" == "" ]]; then
|
||||
unset DOTNET_VERSION
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install by channel or version
|
||||
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
|
||||
if [[ -z ${DOTNET_VERSION+x} ]]; then
|
||||
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
|
||||
else
|
||||
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
|
||||
fi
|
||||
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
|
||||
fi
|
||||
|
||||
echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
|
||||
|
||||
"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
|
||||
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"
|
11
build/.editorconfig
Normal file
11
build/.editorconfig
Normal file
@ -0,0 +1,11 @@
|
||||
[*.cs]
|
||||
dotnet_style_qualification_for_field = false:warning
|
||||
dotnet_style_qualification_for_property = false:warning
|
||||
dotnet_style_qualification_for_method = false:warning
|
||||
dotnet_style_qualification_for_event = false:warning
|
||||
dotnet_style_require_accessibility_modifiers = never:warning
|
||||
|
||||
csharp_style_expression_bodied_methods = true:silent
|
||||
csharp_style_expression_bodied_properties = true:warning
|
||||
csharp_style_expression_bodied_indexers = true:warning
|
||||
csharp_style_expression_bodied_accessors = true:warning
|
96
build/Build.cs
Normal file
96
build/Build.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using System.Linq;
|
||||
using Nuke.Common;
|
||||
using Nuke.Common.CI;
|
||||
using Nuke.Common.CI.GitHubActions;
|
||||
using Nuke.Common.Execution;
|
||||
using Nuke.Common.Git;
|
||||
using Nuke.Common.IO;
|
||||
using Nuke.Common.ProjectModel;
|
||||
using Nuke.Common.Tools.DotNet;
|
||||
using Nuke.Common.Tools.GitVersion;
|
||||
using static Nuke.Common.IO.FileSystemTasks;
|
||||
using static Nuke.Common.Tools.DotNet.DotNetTasks;
|
||||
|
||||
[CheckBuildProjectConfigurations]
|
||||
[ShutdownDotNetAfterServerBuild]
|
||||
[GitHubActions("ci", GitHubActionsImage.UbuntuLatest,
|
||||
OnPushBranches = new[] {"master"},
|
||||
AutoGenerate = true,
|
||||
InvokedTargets = new[] {nameof(Ci)},
|
||||
CacheKeyFiles = new string[0])]
|
||||
class Build : NukeBuild
|
||||
{
|
||||
/// Support plugins are available for:
|
||||
/// - JetBrains ReSharper https://nuke.build/resharper
|
||||
/// - JetBrains Rider https://nuke.build/rider
|
||||
/// - Microsoft VisualStudio https://nuke.build/visualstudio
|
||||
/// - Microsoft VSCode https://nuke.build/vscode
|
||||
public static int Main() => Execute<Build>(x => x.Package);
|
||||
|
||||
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
|
||||
readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
|
||||
|
||||
[Solution] readonly Solution Solution;
|
||||
|
||||
Project SieveProject => Solution.AllProjects.First(p => p.Name == "Sieve");
|
||||
|
||||
[GitRepository] readonly GitRepository GitRepository;
|
||||
|
||||
[GitVersion(Framework = "netcoreapp3.1")] readonly GitVersion GitVersion;
|
||||
|
||||
AbsolutePath OutputDirectory => RootDirectory / "output";
|
||||
|
||||
Target Clean => _ => _
|
||||
.Executes(() =>
|
||||
{
|
||||
DotNetClean();
|
||||
EnsureCleanDirectory(OutputDirectory);
|
||||
});
|
||||
|
||||
Target Restore => _ => _
|
||||
.DependsOn(Clean)
|
||||
.Executes(() =>
|
||||
{
|
||||
DotNetRestore(s => s
|
||||
.SetProjectFile(Solution));
|
||||
});
|
||||
|
||||
Target Compile => _ => _
|
||||
.DependsOn(Restore)
|
||||
.Executes(() =>
|
||||
{
|
||||
DotNetBuild(s => s
|
||||
.SetProjectFile(Solution)
|
||||
.SetConfiguration(Configuration)
|
||||
.SetAssemblyVersion(GitVersion.AssemblySemVer)
|
||||
.SetFileVersion(GitVersion.AssemblySemFileVer)
|
||||
.SetInformationalVersion(GitVersion.InformationalVersion)
|
||||
.EnableNoRestore());
|
||||
});
|
||||
|
||||
Target Test => _ => _
|
||||
.DependsOn(Compile)
|
||||
.Executes(() =>
|
||||
{
|
||||
DotNetTest(s => s
|
||||
.SetProjectFile(Solution)
|
||||
.EnableNoRestore()
|
||||
.EnableNoBuild());
|
||||
});
|
||||
|
||||
Target Package => _ => _
|
||||
.DependsOn(Test)
|
||||
.Executes(() =>
|
||||
{
|
||||
DotNetPack(s => s
|
||||
.SetProject(SieveProject)
|
||||
.SetConfiguration(Configuration)
|
||||
.SetOutputDirectory(OutputDirectory)
|
||||
.SetVersion(GitVersion.NuGetVersionV2)
|
||||
.EnableNoRestore()
|
||||
.EnableNoBuild());
|
||||
});
|
||||
|
||||
Target Ci => _ => _
|
||||
.DependsOn(Package);
|
||||
}
|
16
build/Configuration.cs
Normal file
16
build/Configuration.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Nuke.Common.Tooling;
|
||||
|
||||
[TypeConverter(typeof(TypeConverter<Configuration>))]
|
||||
public class Configuration : Enumeration
|
||||
{
|
||||
public static Configuration Debug = new Configuration { Value = nameof(Debug) };
|
||||
public static Configuration Release = new Configuration { Value = nameof(Release) };
|
||||
|
||||
public static implicit operator string(Configuration configuration)
|
||||
{
|
||||
return configuration.Value;
|
||||
}
|
||||
}
|
8
build/Directory.Build.props
Normal file
8
build/Directory.Build.props
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<!-- This file prevents unintended imports of unrelated MSBuild files -->
|
||||
<!-- Uncomment to include parent Directory.Build.props file -->
|
||||
<!--<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />-->
|
||||
|
||||
</Project>
|
8
build/Directory.Build.targets
Normal file
8
build/Directory.Build.targets
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<!-- This file prevents unintended imports of unrelated MSBuild files -->
|
||||
<!-- Uncomment to include parent Directory.Build.targets file -->
|
||||
<!--<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />-->
|
||||
|
||||
</Project>
|
17
build/_build.csproj
Normal file
17
build/_build.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<RootNamespace></RootNamespace>
|
||||
<NoWarn>CS0649;CS0169</NoWarn>
|
||||
<NukeRootDirectory>..</NukeRootDirectory>
|
||||
<NukeScriptDirectory>..</NukeScriptDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Nuke.Common" Version="5.1.1" />
|
||||
<PackageDownload Include="GitVersion.Tool" Version="[5.6.7]" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
27
build/_build.csproj.DotSettings
Normal file
27
build/_build.csproj.DotSettings
Normal file
@ -0,0 +1,27 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=HeapView_002EDelegateAllocation/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VariableHidesOuterVariable/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassNeverInstantiated_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBeMadeStatic_002ELocal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_INTERNAL_MODIFIER/@EntryValue">Implicit</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_PRIVATE_MODIFIER/@EntryValue">Implicit</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">ExpressionBody</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_MEMBERS/@EntryValue">0</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">False</s:Boolean>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_ATTRIBUTE_LENGTH_FOR_SAME_LINE/@EntryValue">120</s:Int64>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">IF_OWNER_IS_SINGLE_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">WRAP_IF_LONG</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ANONYMOUSMETHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
Loading…
Reference in New Issue
Block a user