├── docs ├── logo.png ├── README.md └── API.md ├── .nuget └── nuget.exe ├── commitlint.config.js ├── cake.config ├── tools └── packages.config ├── src ├── Http.Query.Filter.Client │ ├── Unit.cs │ ├── Filters │ │ ├── IFilterBase.cs │ │ ├── Condition │ │ │ ├── ICondition.cs │ │ │ ├── IWhere.cs │ │ │ ├── StringExtension.cs │ │ │ ├── Field.cs │ │ │ └── Condition.cs │ │ ├── Pagination │ │ │ ├── ISkip.cs │ │ │ └── ILimit.cs │ │ └── ISelect.cs │ ├── Util.cs │ ├── Http.Query.Filter.Client.csproj │ ├── FilterBuilder.cs │ └── IFilterBuilder.cs ├── Http.Query.Filter │ ├── Infrastructure │ │ ├── TryParse{TReturn}.cs │ │ ├── Extensions │ │ │ └── MatchExtension.cs │ │ └── Pattern.cs │ ├── Filters │ │ ├── Ordering │ │ │ ├── OrderByDirection.cs │ │ │ └── OrderBy.cs │ │ ├── Condition │ │ │ ├── Operators │ │ │ │ ├── Logical.cs │ │ │ │ └── Comparison.cs │ │ │ ├── Condition.cs │ │ │ └── Where.cs │ │ ├── Pagination │ │ │ ├── Skip.cs │ │ │ └── Limit.cs │ │ └── Visualization │ │ │ └── Fields.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Http.Query.Filter.csproj │ ├── IFilter.cs │ └── Filter.cs └── Directory.Build.props ├── after.Http.Query.Filter.sln.targets ├── Directory.Build.targets ├── .github ├── ISSUE_TEMPLATE │ ├── 3-help.md │ ├── 2-feature-request.md │ └── 1-bug-report.md └── PULL_REQUEST_TEMPLATE.md ├── .config └── dotnet-tools.json ├── test ├── unit │ ├── Http.Query.Filter.Test │ │ ├── Http.Query.Filter.Test.csproj │ │ ├── Filters │ │ │ ├── SkipTests.cs │ │ │ ├── LimitTests.cs │ │ │ ├── FieldsTests.cs │ │ │ ├── OrderByTests.cs │ │ │ └── WhereTests.cs │ │ └── FilterTests.cs │ └── Http.Query.Filter.Client.Test │ │ ├── Http.Query.Filter.Client.Test.csproj │ │ └── FilterBuilderTests.cs ├── integration │ └── Http.Query.Filter.Integration.Test │ │ ├── Entities │ │ ├── Entity{TId}.cs │ │ └── Account.cs │ │ ├── Http.Query.Filter.Integration.Test.csproj │ │ ├── Infrastructure │ │ ├── Filter │ │ │ ├── IPagination{TReturn,TFilter}.cs │ │ │ ├── ISelect{TFilter,TParam}.cs │ │ │ ├── IWhere{TReturn,TFilter,TParam}.cs │ │ │ └── Extensions │ │ │ │ ├── ObjectExtension.cs │ │ │ │ └── StringExtension.cs │ │ ├── Data │ │ │ └── Linq │ │ │ │ ├── Filter │ │ │ │ ├── Skip.cs │ │ │ │ ├── Limit.cs │ │ │ │ ├── Where{TParam}.cs │ │ │ │ ├── Select{TParam}.cs │ │ │ │ └── WhereExtension.cs │ │ │ │ ├── Extensions │ │ │ │ └── EnumerableExtension.cs │ │ │ │ ├── Queries │ │ │ │ └── Account │ │ │ │ │ └── GetAllQuery.cs │ │ │ │ └── Collections │ │ │ │ └── Accounts.cs │ │ └── Paged{TSource}.cs │ │ └── AccountsTests.cs └── Directory.Build.props ├── .travis.yml ├── Dockerfile ├── docker-compose-ci.yml ├── NuGet.config ├── CONTRIBUTING.md ├── Http.Query.Filter.sln.DotSettings ├── appveyor.yml ├── LICENSE.txt ├── package.json ├── Directory.Build.props ├── .dockerignore ├── .gitignore ├── .gitattributes ├── CHANGELOG.md ├── README.md ├── CODE_OF_CONDUCT.md ├── .editorconfig ├── Http.Query.Filter.sln └── code-analysis.ruleset /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jroliveira/http-query-filter/HEAD/docs/logo.png -------------------------------------------------------------------------------- /.nuget/nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jroliveira/http-query-filter/HEAD/.nuget/nuget.exe -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /cake.config: -------------------------------------------------------------------------------- 1 | ; The configuration file for Cake. 2 | 3 | [Nuget] 4 | Source=https://api.nuget.org/v3/index.json 5 | -------------------------------------------------------------------------------- /tools/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/Unit.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client 2 | { 3 | internal readonly struct Unit 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /after.Http.Query.Filter.sln.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-help.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: '⁉️ Need help?' 3 | about: Please describe the problem. 4 | --- 5 | 6 | 9 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Infrastructure/TryParse{TReturn}.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Infrastructure 2 | { 3 | internal delegate bool TryParse(string input, out TReturn @return); 4 | } 5 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/Filters/IFilterBase.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client.Filters 2 | { 3 | public interface IFilterBase 4 | { 5 | IFilterBuilder Builder { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Filters/Ordering/OrderByDirection.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Filters.Ordering 2 | { 3 | public enum OrderByDirection 4 | { 5 | Descending, 6 | Ascending, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Http.Query.Filter.Test")] 4 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 5 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "cake.tool": { 6 | "version": "0.35.0", 7 | "commands": [ 8 | "dotnet-cake" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Http Query Filter documentation 2 | 3 | * [API documentation](API.md) 4 | * [limit](API.md#limit-) 5 | * [skip](API.md#skip-) 6 | * [order](API.md#order-) 7 | * [where](API.md#where-) 8 | * [fields](API.md#fields-) 9 | -------------------------------------------------------------------------------- /test/unit/Http.Query.Filter.Test/Http.Query.Filter.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Filters/Condition/Operators/Logical.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Filters.Condition.Operators 2 | { 3 | public enum Logical : byte 4 | { 5 | Undefined = 1, 6 | And = 2, 7 | Or = 3, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | sudo: required 3 | dist: xenial 4 | mono: none 5 | dotnet: 3.1 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | install: 12 | - dotnet tool install Cake.Tool --version 0.35.0 13 | 14 | script: 15 | - dotnet cake 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/core/sdk:3.0 2 | 3 | # install cakebuild 0.35.0 4 | RUN dotnet tool install --global Cake.Tool --version 0.35.0 5 | ENV PATH="${PATH}:/root/.dotnet/tools" 6 | 7 | WORKDIR /build 8 | COPY . . 9 | 10 | RUN dotnet cake 11 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/Util.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client 2 | { 3 | internal static partial class Util 4 | { 5 | private static readonly Unit Default = default; 6 | 7 | public static Unit Unit() => Default; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/unit/Http.Query.Filter.Client.Test/Http.Query.Filter.Client.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docker-compose-ci.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | nuget-server: 5 | image: sunside/simple-nuget-server 6 | restart: always 7 | container_name: http-query-filter-nuget-server 8 | ports: 9 | - "9001:80" 10 | environment: 11 | - NUGET_API_KEY=123456 12 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Filters/Condition/Operators/Comparison.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Filters.Condition.Operators 2 | { 3 | public enum Comparison : byte 4 | { 5 | GreaterThan = 1, 6 | LessThan = 2, 7 | Equal = 3, 8 | Inq = 4, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Entities/Entity{TId}.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Entities 2 | { 3 | internal class Entity 4 | { 5 | public Entity(TId id) => this.Id = id; 6 | 7 | public TId Id { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/Http.Query.Filter.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Http.Query.Filter.Client 5 | rest restful api filter http querystring client 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Http Query Filter 2 | 3 | ### No guidelines here, hit us with your pull request. 4 | **Working on your first Pull Request?** You can learn how from this *free* series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) 5 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Infrastructure/Extensions/MatchExtension.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Infrastructure.Extensions 2 | { 3 | using System.Text.RegularExpressions; 4 | 5 | internal static class MatchExtension 6 | { 7 | internal static string GetValue(this Match @this, string group) => @this.Groups[group].Value; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Http.Query.Filter.Integration.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Filter/IPagination{TReturn,TFilter}.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure.Filter 2 | { 3 | using Http.Query.Filter; 4 | 5 | internal interface IPagination 6 | where TFilter : IFilter 7 | { 8 | TReturn Apply(TFilter filter); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Http.Query.Filter.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Http.Query.Filter 5 | rest restful api filter http querystring 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/Filters/Condition/ICondition.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client.Filters.Condition 2 | { 3 | using System.Collections.Generic; 4 | 5 | public interface ICondition 6 | { 7 | IReadOnlyCollection InnerConditions { get; } 8 | 9 | ICondition And(ICondition condition); 10 | 11 | ICondition Or(ICondition condition); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Filter/ISelect{TFilter,TParam}.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure.Filter 2 | { 3 | using System; 4 | 5 | using Http.Query.Filter; 6 | 7 | internal interface ISelect 8 | where TFilter : IFilter 9 | { 10 | Func Apply(TFilter filter); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | netstandard2.1 8 | 9 | 10 | 11 | $(VersionSuffix) 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Filter/IWhere{TReturn,TFilter,TParam}.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure.Filter 2 | { 3 | using System; 4 | 5 | using Http.Query.Filter; 6 | 7 | internal interface IWhere 8 | where TFilter : IFilter 9 | { 10 | Func Apply(TFilter filter); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Data/Linq/Filter/Skip.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure.Data.Linq.Filter 2 | { 3 | using Http.Query.Filter; 4 | using Http.Query.Filter.Integration.Test.Infrastructure.Filter; 5 | 6 | internal readonly struct Skip : IPagination 7 | { 8 | public uint Apply(Filter filter) => filter 9 | .Skip 10 | .GetOrElse(0U); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Data/Linq/Filter/Limit.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure.Data.Linq.Filter 2 | { 3 | using Http.Query.Filter; 4 | using Http.Query.Filter.Integration.Test.Infrastructure.Filter; 5 | 6 | internal readonly struct Limit : IPagination 7 | { 8 | public uint Apply(Filter filter) => filter 9 | .Limit 10 | .GetOrElse(100U); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/FilterBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client 2 | { 3 | using System.Collections.Generic; 4 | 5 | public sealed class FilterBuilder : IFilterBuilder 6 | { 7 | private FilterBuilder() => this.Filters = new List(); 8 | 9 | public ICollection Filters { get; } 10 | 11 | public IFilterBuilder Builder => this; 12 | 13 | public static IFilterBuilder NewFilterBuilder() => new FilterBuilder(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Entities/Account.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Entities 2 | { 3 | internal sealed class Account : Entity 4 | { 5 | internal Account(int id, string email, string password) 6 | : base(id) 7 | { 8 | this.Email = email; 9 | this.Password = password; 10 | } 11 | 12 | public string Email { get; } 13 | 14 | public string Password { get; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Paged{TSource}.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure 2 | { 3 | using System.Collections.Generic; 4 | 5 | internal class Paged 6 | { 7 | internal Paged(IReadOnlyCollection data, uint skip, uint limit) 8 | { 9 | this.Data = data; 10 | this.Skip = skip; 11 | this.Limit = limit; 12 | } 13 | 14 | internal IReadOnlyCollection Data { get; } 15 | 16 | internal uint Skip { get; } 17 | 18 | internal uint Limit { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature request" 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | Please describe the problem you are trying to solve. 14 | 15 | **Describe the solution you'd like** 16 | Please describe the desired behavior. 17 | 18 | **Describe alternatives you've considered** 19 | Please describe alternative solutions or features you have considered. 20 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Filter/Extensions/ObjectExtension.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure.Filter.Extensions 2 | { 3 | using static System.Reflection.BindingFlags; 4 | 5 | internal static class ObjectExtension 6 | { 7 | internal static object GetOrElse(this TObject @this, string property, object @default) => @this == null 8 | ? @default 9 | : @this 10 | .GetType() 11 | .GetProperty(property, IgnoreCase | Public | Instance) 12 | ?.GetValue(@this) ?? @default; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/IFilter.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter 2 | { 3 | using Http.Query.Filter.Filters.Condition; 4 | using Http.Query.Filter.Filters.Ordering; 5 | using Http.Query.Filter.Filters.Pagination; 6 | using Http.Query.Filter.Filters.Visualization; 7 | 8 | public interface IFilter 9 | { 10 | Limit Limit { get; } 11 | 12 | Skip Skip { get; } 13 | 14 | OrderBy OrderBy { get; } 15 | 16 | Where Where { get; } 17 | 18 | Fields Fields { get; } 19 | 20 | bool HasCondition { get; } 21 | 22 | bool HasOrdering { get; } 23 | 24 | bool HasVisualization { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/Filters/Pagination/ISkip.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client.Filters.Pagination 2 | { 3 | public interface ISkip : IFilterBase 4 | { 5 | /// 6 | /// A skip filter omits the specified number of returned records. 7 | /// 8 | /// skip is the number of records to skip. 9 | /// 10 | IFilterBuilder Skip(uint skip) 11 | { 12 | if (skip < 1) 13 | { 14 | skip = 1; 15 | } 16 | 17 | return this.Builder 18 | .AddFilter($"filter[skip]={skip}"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Http.Query.Filter.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="*.Test" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> 3 | <data /> -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/Filters/Condition/IWhere.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client.Filters.Condition 2 | { 3 | public interface IWhere : IFilterBase 4 | { 5 | /// 6 | /// A where filter specifies a set of logical conditions to match, similar to a WHERE clause in a SQL query. 7 | /// 8 | /// 9 | /// 10 | IFilterBuilder Where(ICondition condition) 11 | { 12 | foreach (var filter in condition.InnerConditions) 13 | { 14 | this.Builder.AddFilter(filter.ToString()); 15 | } 16 | 17 | return this.Builder; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/Filters/Condition/StringExtension.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client.Filters.Condition 2 | { 3 | using static Http.Query.Filter.Client.Filters.Condition.Field; 4 | 5 | public static class StringExtension 6 | { 7 | public static ICondition GreaterThan(this string @this, object value) => NewField(@this).GreaterThan(value); 8 | 9 | public static ICondition LessThan(this string @this, object value) => NewField(@this).LessThan(value); 10 | 11 | public static ICondition Equal(this string @this, object value) => NewField(@this).Equal(value); 12 | 13 | public static ICondition Inq(this string @this, params object[] values) => NewField(@this).Inq(values); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/Filters/Pagination/ILimit.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client.Filters.Pagination 2 | { 3 | public interface ILimit : IFilterBase 4 | { 5 | /// 6 | /// A limit filter limits the number of records returned to the specified number (or less). 7 | /// 8 | /// limit is the maximum number of results (records) to return. 9 | /// 10 | IFilterBuilder Limit(uint limit) 11 | { 12 | if (limit < 1) 13 | { 14 | limit = 1; 15 | } 16 | 17 | return this.Builder 18 | .AddFilter($"filter[limit]={limit}"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/Filters/ISelect.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client.Filters 2 | { 3 | public interface ISelect : IFilterBase 4 | { 5 | /// 6 | /// A select filter specifies properties to include from the results. 7 | /// 8 | /// 9 | /// fields are the names of the properties to include from the results. 10 | /// 11 | /// 12 | IFilterBuilder Select(params string[] fields) 13 | { 14 | foreach (var field in fields) 15 | { 16 | this.Builder.AddFilter($"filter[fields][{field}]=true"); 17 | } 18 | 19 | return this.Builder; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | netcoreapp3.0 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Reference: https://www.appveyor.com/docs/appveyor-yml/ 2 | #---------------------------------# 3 | # general configuration # 4 | #---------------------------------# 5 | 6 | branches: 7 | only: 8 | - master 9 | 10 | skip_tags: true 11 | 12 | 13 | #---------------------------------# 14 | # environment configuration # 15 | #---------------------------------# 16 | 17 | image: Visual Studio 2019 18 | 19 | environment: 20 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 21 | DOTNET_CLI_TELEMETRY_OPTOUT: true 22 | 23 | pull_requests: 24 | do_not_increment_build_number: true 25 | 26 | 27 | #---------------------------------# 28 | # build configuration # 29 | #---------------------------------# 30 | 31 | configuration: 32 | - Release 33 | 34 | install: 35 | - cmd: dotnet tool install Cake.Tool --version 0.35.0 36 | 37 | build_script: 38 | - cmd: dotnet cake 39 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Filter/Extensions/StringExtension.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure.Filter.Extensions 2 | { 3 | using Http.Query.Filter.Filters.Condition.Operators; 4 | 5 | using static System.Int32; 6 | using static System.String; 7 | 8 | using static Http.Query.Filter.Filters.Condition.Operators.Comparison; 9 | 10 | internal static class StringExtension 11 | { 12 | internal static bool Verify(this string? @this, string queryValue, Comparison comparison) 13 | { 14 | @this ??= Empty; 15 | 16 | return comparison switch 17 | { 18 | GreaterThan => Parse(@this) > Parse(queryValue), 19 | LessThan => Parse(@this) < Parse(queryValue), 20 | Inq => queryValue == @this, 21 | _ => queryValue == @this, 22 | }; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Data/Linq/Filter/Where{TParam}.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure.Data.Linq.Filter 2 | { 3 | using System; 4 | 5 | using Http.Query.Filter; 6 | using Http.Query.Filter.Integration.Test.Infrastructure.Filter; 7 | 8 | using static Http.Query.Filter.Filters.Condition.Operators.Logical; 9 | 10 | internal readonly struct Where : IWhere 11 | { 12 | public Func Apply(Filter filter) => param => 13 | { 14 | if (param == null) 15 | { 16 | return true; 17 | } 18 | 19 | if (!filter.HasCondition) 20 | { 21 | return true; 22 | } 23 | 24 | var satisfy = filter.Where(param); 25 | 26 | return satisfy(And) && satisfy(Or) && satisfy(Undefined); 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/Filters/Condition/Field.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client.Filters.Condition 2 | { 3 | using static Http.Query.Filter.Client.Filters.Condition.Condition; 4 | 5 | internal sealed class Field 6 | { 7 | private readonly string name; 8 | 9 | private Field(string name) => this.name = name; 10 | 11 | public static implicit operator Field(string name) => new Field(name); 12 | 13 | public override string ToString() => this.name; 14 | 15 | internal static Field NewField(string name) => new Field(name); 16 | 17 | internal ICondition GreaterThan(object value) => NewCondition(this.name, value, "gt"); 18 | 19 | internal ICondition LessThan(object value) => NewCondition(this.name, value, "lt"); 20 | 21 | internal ICondition Equal(object value) => NewCondition(this.name, value); 22 | 23 | internal ICondition Inq(params object[] values) => NewCondition(this.name, values, "inq"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Infrastructure/Pattern.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Infrastructure 2 | { 3 | using System.Text.RegularExpressions; 4 | 5 | using Http.Query.Filter.Infrastructure.Extensions; 6 | 7 | using static System.Text.RegularExpressions.RegexOptions; 8 | 9 | internal sealed class Pattern 10 | { 11 | private readonly Regex regex; 12 | 13 | private Pattern(Regex regex) => this.regex = regex; 14 | 15 | public static implicit operator Pattern(string pattern) => new Pattern(new Regex(pattern, IgnoreCase | Compiled)); 16 | 17 | internal bool TryGetValue(string input, string group, TryParse tryParse, out TReturn @return) 18 | { 19 | @return = default; 20 | 21 | return this.TryMatch(input, out var match) && tryParse(match.GetValue(group), out @return); 22 | } 23 | 24 | internal bool TryMatch(string input, out Match match) 25 | { 26 | match = this.regex.Match(input); 27 | 28 | return match.Success; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Data/Linq/Extensions/EnumerableExtension.cs: -------------------------------------------------------------------------------- 1 | namespace System.Linq 2 | { 3 | using System.Collections.Generic; 4 | 5 | using Http.Query.Filter; 6 | using Http.Query.Filter.Integration.Test.Infrastructure.Data.Linq.Filter; 7 | 8 | using static System.Convert; 9 | 10 | internal static class EnumerableExtension 11 | { 12 | internal static IEnumerable Limit(this IEnumerable @this, Filter filter) => @this.Take(ToInt32(default(Limit).Apply(filter))); 13 | 14 | internal static IEnumerable Skip(this IEnumerable @this, Filter filter) => @this.Skip(ToInt32(default(Skip).Apply(filter))); 15 | 16 | internal static IEnumerable Select(this IEnumerable @this, Filter filter) => @this.Select(default(Select).Apply(filter)); 17 | 18 | internal static IEnumerable Where(this IEnumerable @this, Filter filter) => @this.Where(default(Where).Apply(filter)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Junior Oliveira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Filter.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter 2 | { 3 | using System.Linq; 4 | 5 | using Http.Query.Filter.Filters.Condition; 6 | using Http.Query.Filter.Filters.Ordering; 7 | using Http.Query.Filter.Filters.Pagination; 8 | using Http.Query.Filter.Filters.Visualization; 9 | 10 | public sealed class Filter : IFilter 11 | { 12 | private Filter(string query) 13 | { 14 | this.Limit = query; 15 | this.Skip = query; 16 | this.OrderBy = query; 17 | this.Where = query; 18 | this.Fields = query; 19 | } 20 | 21 | public Limit Limit { get; } 22 | 23 | public Skip Skip { get; } 24 | 25 | public OrderBy OrderBy { get; } 26 | 27 | public Where Where { get; } 28 | 29 | public Fields Fields { get; } 30 | 31 | public bool HasCondition => this.Where.Any(); 32 | 33 | public bool HasOrdering => this.OrderBy.Any(); 34 | 35 | public bool HasVisualization => this.Fields.Any(); 36 | 37 | public static implicit operator Filter(string query) => new Filter(query); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Data/Linq/Filter/Select{TParam}.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure.Data.Linq.Filter 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | using Http.Query.Filter; 8 | using Http.Query.Filter.Integration.Test.Infrastructure.Filter; 9 | using Http.Query.Filter.Integration.Test.Infrastructure.Filter.Extensions; 10 | 11 | internal readonly struct Select : ISelect 12 | { 13 | public Func Apply(Filter filter) => param => 14 | { 15 | if (param == null) 16 | { 17 | return new Dictionary(); 18 | } 19 | 20 | if (!filter.HasVisualization) 21 | { 22 | return param; 23 | } 24 | 25 | var props = new Dictionary(); 26 | 27 | foreach (var (key, _) in filter.Fields.Where(field => field.Value)) 28 | { 29 | props.Add(key, param.GetOrElse(key, new { })); 30 | } 31 | 32 | return props; 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/unit/Http.Query.Filter.Test/Filters/SkipTests.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Test.Filters 2 | { 3 | using FluentAssertions; 4 | 5 | using Http.Query.Filter.Filters.Pagination; 6 | 7 | using Xunit; 8 | 9 | public class SkipTests 10 | { 11 | [Theory] 12 | [InlineData("?filter[skip]=9", 9U)] 13 | [InlineData("?FILTER[SKIP]=9", 9U)] 14 | [InlineData("?filter%5Bskip%5D=1", 1U)] 15 | [InlineData("?skip=9", 9U)] 16 | [InlineData("?SKIP=9", 9U)] 17 | public void Parse_GivenQuery_ValueShouldBe(string query, uint? expected) 18 | { 19 | Skip skip = query; 20 | 21 | uint? actual = skip; 22 | 23 | actual.Should().Be(expected); 24 | } 25 | 26 | [Theory] 27 | [InlineData("?filter[skip]=Nine")] 28 | [InlineData("?filter[skip]=")] 29 | [InlineData("?skip=Nine")] 30 | [InlineData("?skip=")] 31 | [InlineData("")] 32 | [InlineData(default)] 33 | public void Parse_GivenQuery_ShouldReturnNull(string query) 34 | { 35 | Skip skip = query; 36 | 37 | uint? actual = skip; 38 | 39 | actual.Should().BeNull(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/unit/Http.Query.Filter.Test/Filters/LimitTests.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Test.Filters 2 | { 3 | using FluentAssertions; 4 | 5 | using Http.Query.Filter.Filters.Pagination; 6 | 7 | using Xunit; 8 | 9 | public class LimitTests 10 | { 11 | [Theory] 12 | [InlineData("?filter[limit]=9", 9U)] 13 | [InlineData("?FILTER[LIMIT]=9", 9U)] 14 | [InlineData("?filter%5Blimit%5D=9", 9U)] 15 | [InlineData("?limit=9", 9U)] 16 | [InlineData("?LIMIT=9", 9U)] 17 | public void Parse_GivenQuery_ValueShouldBe(string query, uint? expected) 18 | { 19 | Limit limit = query; 20 | 21 | uint? actual = limit; 22 | 23 | actual.Should().Be(expected); 24 | } 25 | 26 | [Theory] 27 | [InlineData("?filter[limit]=Nine")] 28 | [InlineData("?filter[limit]=")] 29 | [InlineData("?limit=Nine")] 30 | [InlineData("?limit=")] 31 | [InlineData("")] 32 | [InlineData(default)] 33 | public void Parse_GivenQuery_ShouldReturnNull(string query) 34 | { 35 | Limit limit = query; 36 | 37 | uint? actual = limit; 38 | 39 | actual.Should().BeNull(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Data/Linq/Queries/Account/GetAllQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure.Data.Linq.Queries.Account 2 | { 3 | using System.Linq; 4 | 5 | using Http.Query.Filter; 6 | using Http.Query.Filter.Integration.Test.Infrastructure.Data.Linq.Collections; 7 | using Http.Query.Filter.Integration.Test.Infrastructure.Filter; 8 | 9 | internal class GetAllQuery 10 | { 11 | private readonly IPagination skip; 12 | private readonly IPagination limit; 13 | 14 | internal GetAllQuery( 15 | IPagination skip, 16 | IPagination limit) 17 | { 18 | this.skip = skip; 19 | this.limit = limit; 20 | } 21 | 22 | internal virtual Paged GetResult(Filter filter) 23 | { 24 | var data = new Accounts() 25 | .Skip(filter) 26 | .Limit(filter) 27 | .Where(filter) 28 | .Select(filter) 29 | .ToList(); 30 | 31 | return new Paged( 32 | data, 33 | this.skip.Apply(filter), 34 | this.limit.Apply(filter)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Data/Linq/Collections/Accounts.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure.Data.Linq.Collections 2 | { 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | 6 | using Http.Query.Filter.Integration.Test.Entities; 7 | 8 | internal sealed class Accounts : ReadOnlyCollection 9 | { 10 | private static readonly IList DefaultData = new List 11 | { 12 | new Account(01, "junoliv.e@gmail.com", "123456"), 13 | new Account(02, "junoli.ve@gmail.com", "654321"), 14 | new Account(03, "junol.ive@gmail.com", "111111"), 15 | new Account(04, "jun.olive@gmail.com", "222222"), 16 | new Account(05, "j.unolive@gmail.com", "333333"), 17 | new Account(06, "ju.nolive@gmail.com", "444444"), 18 | new Account(07, "jun.olive@gmail.com", "555555"), 19 | new Account(08, "juno.live@gmail.com", "666666"), 20 | new Account(09, "junol.ive@gmail.com", "777777"), 21 | new Account(10, "junoli.ve@gmail.com", "888888"), 22 | new Account(11, "junoliv.e@gmail.com", "999999"), 23 | }; 24 | 25 | internal Accounts() 26 | : base(DefaultData) 27 | { 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Filters/Pagination/Skip.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Filters.Pagination 2 | { 3 | using Http.Query.Filter.Infrastructure; 4 | 5 | using static System.Net.WebUtility; 6 | using static System.String; 7 | using static System.UInt32; 8 | 9 | public readonly struct Skip 10 | { 11 | private static readonly Pattern[] Patterns = 12 | { 13 | @"filter\[skip]\=(?\d+)", 14 | @"skip\=(?\d+)", 15 | }; 16 | 17 | private readonly uint? value; 18 | 19 | private Skip(uint? value) => this.value = value; 20 | 21 | public static implicit operator uint?(Skip skip) => skip.value; 22 | 23 | public static implicit operator Skip(string query) 24 | { 25 | if (IsNullOrWhiteSpace(query)) 26 | { 27 | return default; 28 | } 29 | 30 | var decodedQuery = UrlDecode(query); 31 | 32 | foreach (var pattern in Patterns) 33 | { 34 | if (pattern.TryGetValue(decodedQuery, "skip", TryParse, out uint skip)) 35 | { 36 | return new Skip(skip); 37 | } 38 | } 39 | 40 | return default; 41 | } 42 | 43 | public uint GetOrElse(uint @default) => this.value ?? @default; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Filters/Pagination/Limit.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Filters.Pagination 2 | { 3 | using Http.Query.Filter.Infrastructure; 4 | 5 | using static System.Net.WebUtility; 6 | using static System.String; 7 | using static System.UInt32; 8 | 9 | public readonly struct Limit 10 | { 11 | private static readonly Pattern[] Patterns = 12 | { 13 | @"filter\[limit]\=(?\d+)", 14 | @"limit\=(?\d+)", 15 | }; 16 | 17 | private readonly uint? value; 18 | 19 | private Limit(uint? value) => this.value = value; 20 | 21 | public static implicit operator uint?(Limit limit) => limit.value; 22 | 23 | public static implicit operator Limit(string query) 24 | { 25 | if (IsNullOrWhiteSpace(query)) 26 | { 27 | return default; 28 | } 29 | 30 | var decodedQuery = UrlDecode(query); 31 | 32 | foreach (var pattern in Patterns) 33 | { 34 | if (pattern.TryGetValue(decodedQuery, "limit", TryParse, out uint limit)) 35 | { 36 | return new Limit(limit); 37 | } 38 | } 39 | 40 | return default; 41 | } 42 | 43 | public uint GetOrElse(uint @default) => this.value ?? @default; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a bug report 4 | --- 5 | 6 | 7 | 8 | ## Expected Behavior 9 | 10 | 11 | 12 | 13 | ## Current Behavior 14 | 15 | 16 | 17 | 18 | ## Possible Solution 19 | 20 | 21 | 22 | 23 | ## Steps to Reproduce (for bugs) 24 | 25 | 26 | 27 | 28 | 1. 29 | 2. 30 | 3. 31 | 4. 32 | 33 | ## Context 34 | 35 | 36 | 37 | 38 | ## Your Environment 39 | 40 | 41 | 42 | - Library Version used: 43 | - Operating System and version: 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-query-filter", 3 | "version": "3.0.32", 4 | "description": "Project-based filter system StrongLoop Node.js API Platform developed by IBM Company.", 5 | "homepage": "https://github.com/jroliveira/http-query-filter#readme", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jroliveira/http-query-filter.git" 9 | }, 10 | "keywords": [ 11 | "csharp", 12 | "rest", 13 | "package", 14 | "http", 15 | "filter", 16 | "querystring", 17 | "dotnet-standard" 18 | ], 19 | "author": "Junior Oliveira (https://jroliveira.net)", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/jroliveira/http-query-filter/issues" 23 | }, 24 | "readmeFilename": "README.md", 25 | "scripts": { 26 | "test": "echo \"Error: no test specified\" && exit 1", 27 | "release": "standard-version", 28 | "postinstall": "dotnet tool install Cake.Tool --version 0.35.0" 29 | }, 30 | "devDependencies": { 31 | "@commitlint/cli": "9.1.1", 32 | "@commitlint/config-conventional": "9.1.1", 33 | "cz-conventional-changelog": "3.2.0", 34 | "husky": "4.2.5", 35 | "standard-version": "8.0.2" 36 | }, 37 | "husky": { 38 | "hooks": { 39 | "pre-push": "dotnet cake", 40 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 41 | } 42 | }, 43 | "config": { 44 | "commitizen": { 45 | "path": "./node_modules/cz-conventional-changelog" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Filters/Condition/Condition.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Filters.Condition 2 | { 3 | using Http.Query.Filter.Filters.Condition.Operators; 4 | 5 | using static Http.Query.Filter.Filters.Condition.Operators.Comparison; 6 | using static Http.Query.Filter.Filters.Condition.Operators.Logical; 7 | 8 | public sealed class Condition 9 | { 10 | internal Condition(string field, string value) 11 | : this(field, value, Equal, Undefined, 0) 12 | { 13 | } 14 | 15 | internal Condition(string field, string value, Comparison comparison) 16 | : this(field, value, comparison, Undefined, 0) 17 | { 18 | } 19 | 20 | internal Condition(string field, string value, Logical logical, ushort index) 21 | : this(field, value, Equal, logical, index) 22 | { 23 | } 24 | 25 | internal Condition(string field, string value, Comparison comparison, Logical logical, ushort index) 26 | { 27 | this.Field = field; 28 | this.Value = value; 29 | this.Comparison = comparison; 30 | this.Logical = logical; 31 | this.Index = index; 32 | } 33 | 34 | public string Field { get; } 35 | 36 | public string Value { get; } 37 | 38 | public Comparison Comparison { get; } 39 | 40 | public Logical Logical { get; } 41 | 42 | public ushort Index { get; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/Infrastructure/Data/Linq/Filter/WhereExtension.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test.Infrastructure.Data.Linq.Filter 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | using Http.Query.Filter.Filters.Condition; 8 | using Http.Query.Filter.Filters.Condition.Operators; 9 | using Http.Query.Filter.Integration.Test.Infrastructure.Filter.Extensions; 10 | 11 | using static System.String; 12 | 13 | using static Http.Query.Filter.Filters.Condition.Operators.Logical; 14 | 15 | internal static class WhereExtension 16 | { 17 | internal static Func Where(this IFilter @this, TParam param) => logical => 18 | { 19 | var conditions = new List(@this 20 | .Where 21 | .Where(condition => condition.Logical == logical)); 22 | 23 | if (!conditions.Any()) 24 | { 25 | return true; 26 | } 27 | 28 | var satisfy = Satisfy(param); 29 | 30 | return logical switch 31 | { 32 | Or => conditions.Any(satisfy), 33 | And => conditions.All(satisfy), 34 | _ => conditions.Any(satisfy) 35 | }; 36 | }; 37 | 38 | private static Func? Satisfy(TParam param) => condition => param 39 | .GetOrElse(condition.Field, Empty) 40 | .ToString() 41 | .Verify(condition.Value, condition.Comparison); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Types of changes 8 | 9 | 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 14 | 15 | ## Related Issue 16 | 17 | 18 | 19 | 20 | 21 | 22 | ## Motivation and Context 23 | 24 | 25 | 26 | ## How Has This Been Tested? 27 | 28 | 29 | 30 | 31 | 32 | 33 | ## Screenshots (if appropriate): 34 | 35 | ## Checklist: 36 | 37 | 38 | 39 | 40 | - [ ] I have updated the documentation (if required). 41 | - [ ] I have read the **CONTRIBUTING** document. 42 | - [ ] I have added tests to cover my changes. 43 | - [ ] All new and existing tests passed. 44 | - [ ] I added a picture of a cute animal cause it's fun 45 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Filters/Visualization/Fields.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Filters.Visualization 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.Linq; 7 | using System.Text.RegularExpressions; 8 | 9 | using Http.Query.Filter.Infrastructure.Extensions; 10 | 11 | using static System.Net.WebUtility; 12 | using static System.String; 13 | using static System.Text.RegularExpressions.RegexOptions; 14 | 15 | public sealed class Fields : ReadOnlyCollection> 16 | { 17 | private const string Pattern = @"filter\[fields]\[(?\w+)]\=(?true|false)"; 18 | 19 | private static readonly Func Matches = new Regex(Pattern, IgnoreCase | Compiled).Matches; 20 | private static readonly IReadOnlyDictionary Types = new Dictionary 21 | { 22 | { "true", true }, 23 | { "false", false }, 24 | }; 25 | 26 | internal Fields(IEnumerable> fields) 27 | : base(fields.ToList()) 28 | { 29 | } 30 | 31 | public static implicit operator Fields(string query) => new Fields(GetFields(query)); 32 | 33 | private static IEnumerable> GetFields(string query) => IsNullOrWhiteSpace(query) 34 | ? new List>() 35 | : new List>( 36 | from Match match in Matches(UrlDecode(query)) 37 | let field = match.GetValue("field") 38 | let show = GetShow(match) 39 | select new KeyValuePair(field, show)); 40 | 41 | private static bool GetShow(Match match) => Types[match.GetValue("show").ToLower()]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/IFilterBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | 7 | using Http.Query.Filter.Client.Filters; 8 | using Http.Query.Filter.Client.Filters.Condition; 9 | using Http.Query.Filter.Client.Filters.Pagination; 10 | 11 | using static System.String; 12 | using static System.Threading.Tasks.Task; 13 | 14 | using static Http.Query.Filter.Client.Util; 15 | 16 | public interface IFilterBuilder : ISkip, ILimit, ISelect, IWhere 17 | { 18 | ICollection Filters { get; } 19 | 20 | /// 21 | /// Performs the operation with selected filters. 22 | /// 23 | /// done is a function that will be executed after compiling the filters. 24 | /// Returns task. 25 | Task Build(Func done) => this.Build(queryString => 26 | { 27 | done(queryString); 28 | return FromResult(Unit()); 29 | }); 30 | 31 | /// 32 | /// Performs the operation with selected filters. 33 | /// 34 | /// done is a function that will be executed after compiling the filters. 35 | /// Returns the API data. 36 | Task Build(Func> done) => done(this.Build()); 37 | 38 | /// 39 | /// Performs the operation with selected filters. 40 | /// 41 | /// Returns the query string. 42 | string Build() => Join("&", this.Filters); 43 | 44 | internal IFilterBuilder AddFilter(string filter) 45 | { 46 | this.Filters.Add(filter); 47 | return this; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Filters/Ordering/OrderBy.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Filters.Ordering 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.Linq; 7 | using System.Text.RegularExpressions; 8 | 9 | using Http.Query.Filter.Infrastructure.Extensions; 10 | 11 | using static System.Net.WebUtility; 12 | using static System.String; 13 | using static System.Text.RegularExpressions.RegexOptions; 14 | using static Http.Query.Filter.Filters.Ordering.OrderByDirection; 15 | 16 | public sealed class OrderBy : ReadOnlyCollection> 17 | { 18 | private const string Pattern = @"filter\[order](\[\d+])?\=(?\w+)\s(?asc|desc)"; 19 | 20 | private static readonly Func Matches = new Regex(Pattern, IgnoreCase | Compiled).Matches; 21 | private static readonly IReadOnlyDictionary Types = new Dictionary 22 | { 23 | { "asc", Ascending }, 24 | { "desc", Descending }, 25 | }; 26 | 27 | internal OrderBy(IEnumerable> fields) 28 | : base(fields.ToList()) 29 | { 30 | } 31 | 32 | public static implicit operator OrderBy(string query) => new OrderBy(GetFields(query)); 33 | 34 | private static IEnumerable> GetFields(string query) => IsNullOrEmpty(query) 35 | ? new List>() 36 | : new List>( 37 | from Match match in Matches(UrlDecode(query)) 38 | let field = match.GetValue("field") 39 | let orderBy = GetDirection(match) 40 | select new KeyValuePair(field, orderBy)); 41 | 42 | private static OrderByDirection GetDirection(Match match) => Types[match.GetValue("direction").ToLower()]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | C# 6 | 8.0 7 | 1591 8 | 9 | git 10 | https://github.com/jroliveira/http-query-filter 11 | 12 | Junior Oliveira 13 | Junior Oliveira 14 | Project-based filter system StrongLoop Node.js API Platform developed by IBM Company. 15 | © Copyright (c) 2019 Junior Oliveira. 16 | https://github.com/jroliveira/http-query-filter 17 | https://raw.githubusercontent.com/jroliveira/http-query-filter/master/docs/logo.png 18 | 19 | 20 | 21 | 22 | enable 23 | enable 24 | true 25 | 26 | 27 | 28 | 29 | LICENSE.txt 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | https://github.com/jroliveira/http-query-filter/blob/master/LICENSE.txt 40 | 41 | NU5125 42 | 43 | 44 | 45 | $(MSBuildThisFileDirectory)code-analysis.ruleset 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | [Tt]emp 95 | [Pp]ackages 96 | [Aa]rtifacts 97 | sql 98 | TestResults 99 | [Tt]est[Rr]esult* 100 | *.Cache 101 | ClientBin 102 | [Ss]tyle[Cc]op.* 103 | ~$* 104 | *.dbmdl 105 | Generated_Code #added for RIA/Silverlight projects 106 | 107 | # Backup & report files from converting an old project file to a newer 108 | # Visual Studio version. Backup files are not needed, because we have git ;-) 109 | _UpgradeReport_Files/ 110 | Backup*/ 111 | UpgradeLog*.XML 112 | .vs 113 | 114 | # App config related 115 | appsettings.json 116 | api.env 117 | 118 | # Build related 119 | tools/** 120 | !tools/packages.config 121 | 122 | CONTRIBUTING.md 123 | README.md 124 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # NPM Packages Directory 85 | node_modules 86 | npm-debug.log* 87 | package-lock.json 88 | 89 | # Windows Azure Build Output 90 | csx 91 | *.build.csdef 92 | 93 | # Windows Store app package directory 94 | AppPackages/ 95 | 96 | # Others 97 | [Bb]in 98 | [Oo]bj 99 | [Tt]emp 100 | [Pp]ackages 101 | [Aa]rtifacts 102 | sql 103 | TestResults 104 | [Tt]est[Rr]esult* 105 | *.Cache 106 | ClientBin 107 | [Ss]tyle[Cc]op.* 108 | ~$* 109 | *.dbmdl 110 | Generated_Code #added for RIA/Silverlight projects 111 | 112 | # Backup & report files from converting an old project file to a newer 113 | # Visual Studio version. Backup files are not needed, because we have git ;-) 114 | _UpgradeReport_Files/ 115 | Backup*/ 116 | UpgradeLog*.XML 117 | .vs 118 | 119 | # App config related 120 | appsettings.json 121 | ci-env.json 122 | api.env 123 | 124 | # Build related 125 | tools/** 126 | !tools/packages.config 127 | -------------------------------------------------------------------------------- /test/unit/Http.Query.Filter.Test/Filters/FieldsTests.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Test.Filters 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | using FluentAssertions; 8 | 9 | using Http.Query.Filter.Filters.Visualization; 10 | 11 | using Xunit; 12 | 13 | public class FieldsTests 14 | { 15 | [Theory] 16 | [ClassData(typeof(TestData))] 17 | public void Parse_GivenQuery_ShouldReturn(string query, Fields expected) 18 | { 19 | Fields actual = query; 20 | 21 | actual.Should().BeEquivalentTo(expected); 22 | } 23 | 24 | [Theory] 25 | [InlineData("?filter[fields][id]=1")] 26 | [InlineData("?filter[fields][id]=")] 27 | public void Parse_GivenQuery_ShouldReturnEmpty(string query) 28 | { 29 | Fields actual = query; 30 | 31 | actual.Should().BeEmpty(); 32 | } 33 | 34 | public class TestData : IEnumerable 35 | { 36 | private static readonly Func Field = (key, value) => Fields(new List> 37 | { 38 | new KeyValuePair(key, value), 39 | }); 40 | 41 | private static readonly Func>, Fields> Fields = data => new Fields(data); 42 | 43 | public IEnumerator GetEnumerator() 44 | { 45 | yield return new object[] { "?filter[fields][id]=true", Field("id", true) }; 46 | yield return new object[] { "?filter[fields][id]=false", Field("id", false) }; 47 | yield return new object[] { "?FILTER[FIELDS][ID]=TRUE", Field("ID", true) }; 48 | yield return new object[] { "?filter%5Bfields%5D%5Bid%5D=false", Field("id", false) }; 49 | yield return new object[] 50 | { 51 | "?filter[fields][id]=true&filter[fields][name]=false", 52 | Fields(new List> 53 | { 54 | new KeyValuePair("id", true), 55 | new KeyValuePair("name", false), 56 | }), 57 | }; 58 | } 59 | 60 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/unit/Http.Query.Filter.Test/Filters/OrderByTests.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Test.Filters 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | using FluentAssertions; 8 | 9 | using Http.Query.Filter.Filters.Ordering; 10 | 11 | using Xunit; 12 | 13 | using static Http.Query.Filter.Filters.Ordering.OrderByDirection; 14 | 15 | public class OrderByTests 16 | { 17 | [Theory] 18 | [ClassData(typeof(TestData))] 19 | public void Parse_GivenQuery_ShouldReturn(string query, OrderBy expected) 20 | { 21 | OrderBy actual = query; 22 | 23 | actual.Should().BeEquivalentTo(expected); 24 | } 25 | 26 | [Theory] 27 | [InlineData("?filter[order]=name%20des")] 28 | [InlineData("?filter[order]=last name asc")] 29 | public void Parse_GivenQuery_ShouldReturnEmpty(string query) 30 | { 31 | OrderBy actual = query; 32 | 33 | actual.Should().BeEmpty(); 34 | } 35 | 36 | public class TestData : IEnumerable 37 | { 38 | private static readonly Func OrderBy = (key, value) => OrderByData(new List> 39 | { 40 | new KeyValuePair(key, value), 41 | }); 42 | 43 | private static readonly Func>, OrderBy> OrderByData = data => new OrderBy(data); 44 | 45 | public IEnumerator GetEnumerator() 46 | { 47 | yield return new object[] { "?filter[order]=id asc", OrderBy("id", Ascending) }; 48 | yield return new object[] { "?FILTER[ORDER]=ID DESC", OrderBy("ID", Descending) }; 49 | yield return new object[] { "?filter%5Border%5D=id%20asc", OrderBy("id", Ascending) }; 50 | yield return new object[] 51 | { 52 | "?filter[order][0]=id asc&filter[order][1]=name desc", OrderByData(new List> 53 | { 54 | new KeyValuePair("id", Ascending), 55 | new KeyValuePair("name", Descending), 56 | }), 57 | }; 58 | } 59 | 60 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=lf 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/Http.Query.Filter.Client/Filters/Condition/Condition.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client.Filters.Condition 2 | { 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | using static System.String; 7 | 8 | internal sealed class Condition : ICondition 9 | { 10 | private readonly Field field; 11 | private readonly object[] values; 12 | private readonly string comparison; 13 | private readonly List conditions; 14 | 15 | private string? logical; 16 | 17 | private Condition(Field field, object[] values, string comparison) 18 | { 19 | this.field = field; 20 | this.values = values; 21 | this.comparison = comparison; 22 | this.conditions = new List { this }; 23 | } 24 | 25 | public IReadOnlyCollection InnerConditions => new List(this.conditions); 26 | 27 | public override string ToString() 28 | { 29 | var result = new StringBuilder(); 30 | 31 | foreach (var value in this.values) 32 | { 33 | result.Append("filter[where]"); 34 | 35 | if (!IsNullOrEmpty(this.logical)) 36 | { 37 | result.Append($"[{this.logical}]"); 38 | } 39 | 40 | result.Append($"[{this.field}]"); 41 | 42 | if (!IsNullOrEmpty(this.comparison)) 43 | { 44 | result.Append($"[{this.comparison}]"); 45 | } 46 | 47 | result.Append($"={value}&"); 48 | } 49 | 50 | return result 51 | .ToString() 52 | .Remove(result.Length - 1); 53 | } 54 | 55 | public ICondition And(ICondition condition) 56 | { 57 | this.logical = "and"; 58 | 59 | var otherCondition = (Condition)condition; 60 | otherCondition.logical ??= "and"; 61 | this.conditions.Add(otherCondition); 62 | 63 | return this; 64 | } 65 | 66 | public ICondition Or(ICondition condition) 67 | { 68 | this.logical = "or"; 69 | 70 | var otherCondition = (Condition)condition; 71 | otherCondition.logical ??= "or"; 72 | this.conditions.AddRange(condition.InnerConditions); 73 | 74 | return this; 75 | } 76 | 77 | internal static Condition NewCondition(Field field, object value, string comparison = "") => new Condition(field, new[] { value }, comparison); 78 | 79 | internal static Condition NewCondition(Field field, object[] values, string comparison = "") => new Condition(field, values, comparison); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [3.0.32](https://github.com/jroliveira/http-query-filter/compare/3.0.31...3.0.32) (2020-07-16) 6 | 7 | 8 | ### Features 9 | 10 | * update dependencies ([7fa8a2b](https://github.com/jroliveira/http-query-filter/commit/7fa8a2bc6341cc9b26e8d07d1e26dea0e6f04b9c)) 11 | 12 | ### [3.0.31](https://github.com/jroliveira/http-query-filter/compare/3.0.30...3.0.31) (2020-06-08) 13 | 14 | 15 | ### Features 16 | 17 | * add inq operator ([b210a1c](https://github.com/jroliveira/http-query-filter/commit/b210a1c)) 18 | 19 | 20 | 21 | ### [3.0.30](https://github.com/jroliveira/http-query-filter/compare/3.0.29...3.0.30) (2020-03-15) 22 | 23 | 24 | 25 | ### [3.0.29](https://github.com/jroliveira/http-query-filter/compare/3.0.28...3.0.29) (2020-02-27) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * change the way the cakebuild dependency is installed in ci ([570cc20](https://github.com/jroliveira/http-query-filter/commit/570cc20)) 31 | 32 | 33 | ### Features 34 | 35 | * add logical operators (and, or) ([a87d8ba](https://github.com/jroliveira/http-query-filter/commit/a87d8ba)) 36 | * add skip and limit with RQL syntax ([5492954](https://github.com/jroliveira/http-query-filter/commit/5492954)) 37 | 38 | 39 | 40 | ### 3.0.28 (2019-08-11) 41 | 42 | 43 | ### Bug Fixes 44 | 45 | * appveyor build ([0cba4f6](https://github.com/jroliveira/http-query-filter/commit/0cba4f6)) 46 | * broken nuget badge link ([4088160](https://github.com/jroliveira/http-query-filter/commit/4088160)) 47 | * change ci configuration ([8effe98](https://github.com/jroliveira/http-query-filter/commit/8effe98)) 48 | * change ci configuration ([008db4a](https://github.com/jroliveira/http-query-filter/commit/008db4a)) 49 | * change stylecop rules ([f9ba96d](https://github.com/jroliveira/http-query-filter/commit/f9ba96d)) 50 | * change travis-ci to building specific branches ([df894fd](https://github.com/jroliveira/http-query-filter/commit/df894fd)) 51 | * update appveyor ([4e4d951](https://github.com/jroliveira/http-query-filter/commit/4e4d951)) 52 | 53 | 54 | ### Features 55 | 56 | * add comparison filter between field and value ([3515e50](https://github.com/jroliveira/http-query-filter/commit/3515e50)) 57 | * add filter to remove field from query return ([0d33208](https://github.com/jroliveira/http-query-filter/commit/0d33208)) 58 | * add http query filter client sdk ([c5a82b2](https://github.com/jroliveira/http-query-filter/commit/c5a82b2)) 59 | * add operator and/or in comparison filter ([a53b87c](https://github.com/jroliveira/http-query-filter/commit/a53b87c)) 60 | * add sorting filter with multiple fields ([8feb7bc](https://github.com/jroliveira/http-query-filter/commit/8feb7bc)) 61 | * add url decode ([3116ab1](https://github.com/jroliveira/http-query-filter/commit/3116ab1)) 62 | -------------------------------------------------------------------------------- /src/Http.Query.Filter/Filters/Condition/Where.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Filters.Condition 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.Linq; 7 | using System.Text.RegularExpressions; 8 | 9 | using Http.Query.Filter.Filters.Condition.Operators; 10 | using Http.Query.Filter.Infrastructure.Extensions; 11 | 12 | using static System.Net.WebUtility; 13 | using static System.String; 14 | using static System.Text.RegularExpressions.RegexOptions; 15 | using static Http.Query.Filter.Filters.Condition.Operators.Comparison; 16 | using static Http.Query.Filter.Filters.Condition.Operators.Logical; 17 | 18 | public sealed class Where : ReadOnlyCollection 19 | { 20 | private const string Pattern = @"filter\[where]?(\[(?and|or)\])?(\[(?\d+)\])?(\[(?\w+)\])?(\[(?gt|lt|inq)\])?=(?[^&]*)&?"; 21 | 22 | private static readonly Func Matches = new Regex(Pattern, IgnoreCase | Compiled).Matches; 23 | private static readonly IReadOnlyDictionary ComparisonOperations = new Dictionary 24 | { 25 | { "gt", GreaterThan }, 26 | { "lt", LessThan }, 27 | { "inq", Inq }, 28 | }; 29 | 30 | private static readonly IReadOnlyDictionary LogicalOperations = new Dictionary 31 | { 32 | { "and", And }, 33 | { "or", Or }, 34 | }; 35 | 36 | internal Where(IEnumerable conditions) 37 | : base(conditions.ToList()) 38 | { 39 | } 40 | 41 | public static implicit operator Where(string query) => new Where(GetConditions(query)); 42 | 43 | private static IEnumerable GetConditions(string query) => IsNullOrWhiteSpace(query) 44 | ? new List() 45 | : new List( 46 | from Match match in Matches(UrlDecode(query)) 47 | let field = match.GetValue("field") 48 | let value = match.GetValue("value") 49 | let comparison = GetOrElseOperator(match, "comparison", ComparisonOperations, Equal) 50 | let logical = GetOrElseOperator(match, "logical", LogicalOperations, Undefined) 51 | let index = ushort.TryParse(match.GetValue("index"), out var number) ? number : default 52 | select new Condition(field, value, comparison, logical, index)); 53 | 54 | private static TOperator GetOrElseOperator( 55 | Match match, 56 | string operatorName, 57 | IReadOnlyDictionary operations, 58 | TOperator @default) 59 | { 60 | var operation = match.GetValue(operatorName).ToLower(); 61 | 62 | return IsNullOrEmpty(operation) 63 | ? @default 64 | : operations[operation]; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Http Query Filter - Logo][logo] 2 | 3 | # Http Query Filter 4 | 5 | [![Build status](https://ci.appveyor.com/api/projects/status/id8it23ojpmcwlbb?svg=true)](https://ci.appveyor.com/project/junioro/http-query-filter) 6 | [![Build Status](https://travis-ci.org/jroliveira/http-query-filter.svg?branch=master)](https://travis-ci.org/jroliveira/http-query-filter) 7 | [![NuGet](https://img.shields.io/nuget/v/Http.Query.Filter.svg)](https://www.nuget.org/packages/Http.Query.Filter/) 8 | [![NuGet](https://img.shields.io/nuget/dt/Http.Query.Filter.svg)](https://www.nuget.org/packages/Http.Query.Filter/) 9 | [![CodeFactor](https://www.codefactor.io/repository/github/jroliveira/http-query-filter/badge)](https://www.codefactor.io/repository/github/jroliveira/http-query-filter) 10 | [![Maintainability](https://api.codeclimate.com/v1/badges/62b3f82f9fc66560bbd7/maintainability)](https://codeclimate.com/github/jroliveira/http-query-filter/maintainability) 11 | [![License: MIT](http://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt) 12 | 13 | Project-based filter system [LoopBack Querying data][loopback] developed by [IBM Company][ibm] and [Resource Query Language (RQL)][rql]. 14 | 15 | ## Installing / Getting started 16 | 17 | ``` bash 18 | # Install package 19 | $ dotnet add package Http.Query.Filter 20 | ``` 21 | 22 | ## Developing 23 | 24 | ### Built With 25 | 26 | - [.NET Core](https://docs.microsoft.com/en-us/dotnet/core/) 27 | - [C#](https://docs.microsoft.com/en-us/dotnet/csharp/) 28 | - [Node.js](https://nodejs.org/en/) 29 | 30 | ### Pre requisites 31 | 32 | Download and install: 33 | 34 | - [.NET Core SDK](https://www.microsoft.com/net/download) 35 | - [Node.js](https://nodejs.org/en/download/) 36 | 37 | ### Setting up Dev 38 | 39 | ``` bash 40 | # Clone this repository 41 | $ git clone https://github.com/jroliveira/http-query-filter.git 42 | 43 | # Go into the repository 44 | $ cd http-query-filter 45 | 46 | # Download node packages and install Cake 47 | $ npm install 48 | ``` 49 | 50 | ### Building 51 | 52 | ``` bash 53 | $ dotnet cake 54 | ``` 55 | 56 | or simulating ci 57 | 58 | ``` bash 59 | $ dotnet cake --target=Release --simulating-ci 60 | ``` 61 | 62 | or with docker 63 | 64 | ``` bash 65 | $ docker build --tag http-query-filter . 66 | ``` 67 | 68 | ### Testing 69 | 70 | ``` bash 71 | $ dotnet cake 72 | ``` 73 | 74 | or 75 | 76 | ``` bash 77 | $ dotnet test 78 | ``` 79 | 80 | ### Releasing 81 | 82 | You must create a file `ci-env.json` on the path `./build/` with the context below. 83 | This file is used to set the configuration to run the command below. 84 | 85 | ``` json 86 | { 87 | "nuget": { 88 | "apiKey": "" 89 | } 90 | } 91 | ``` 92 | 93 | ``` bash 94 | $ dotnet cake --target=Release 95 | ``` 96 | 97 | ## Api Reference 98 | 99 | [Documentation](docs/README.md) 100 | 101 | ## Licensing 102 | 103 | The code is available under the [MIT license](LICENSE.txt). 104 | 105 | [loopback]: https://loopback.io/doc/en/lb4/Querying-data.html 106 | [ibm]: http://www.ibm.com/ 107 | [logo]: docs/logo.png "Http Query Filter - Logo" 108 | [rql]: https://github.com/persvr/rql 109 | -------------------------------------------------------------------------------- /test/unit/Http.Query.Filter.Test/FilterTests.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Test 2 | { 3 | using FluentAssertions; 4 | 5 | using Xunit; 6 | 7 | public class FilterTests 8 | { 9 | private const string Query = @" 10 | ?filter%5Bskip%5D=1 11 | &filter%5Blimit%5D=2 12 | &filter%5Border%5D%5B0%5D=id%20desc 13 | &filter%5Border%5D%5B1%5D=name%20asc 14 | &filter%5Bwhere%5D%5Bid%5D=2 15 | &filter%5Bwhere%5D%5Bid%5D=4 16 | &filter%5Bfields%5D%5Bid%5D=false"; 17 | 18 | private const string QueryDecoded = @" 19 | ?filter[skip]=1 20 | &filter[limit]=2 21 | &filter[order][0]=id desc 22 | &filter[order][1]=name asc 23 | &filter[where][id]=2 24 | &filter[where][id]=4 25 | &filter[fields][id]=false"; 26 | 27 | [Theory] 28 | [InlineData(Query)] 29 | [InlineData(QueryDecoded)] 30 | public void Parse_GivenQuery_SkipCannotBeNull(string query) 31 | { 32 | Filter actual = query; 33 | 34 | actual.Skip.Should().NotBeNull(); 35 | } 36 | 37 | [Theory] 38 | [InlineData(Query)] 39 | [InlineData(QueryDecoded)] 40 | public void Parse_GivenQuery_LimitCannotBeNull(string query) 41 | { 42 | Filter actual = query; 43 | 44 | actual.Limit.Should().NotBeNull(); 45 | } 46 | 47 | [Theory] 48 | [InlineData(Query)] 49 | [InlineData(QueryDecoded)] 50 | public void Parse_GivenQuery_OrderByCannotBeNull(string query) 51 | { 52 | Filter actual = query; 53 | 54 | actual.OrderBy.Should().NotBeNull(); 55 | actual.HasOrdering.Should().BeTrue(); 56 | } 57 | 58 | [Theory] 59 | [InlineData(Query)] 60 | [InlineData(QueryDecoded)] 61 | public void Parse_GivenQuery_OrderByMustContainTwoElements(string query) 62 | { 63 | Filter actual = query; 64 | 65 | actual.OrderBy.Count.Should().Be(2); 66 | } 67 | 68 | [Theory] 69 | [InlineData(Query)] 70 | [InlineData(QueryDecoded)] 71 | public void Parse_GivenQuery_WhereCannotBeNull(string query) 72 | { 73 | Filter actual = query; 74 | 75 | actual.Where.Should().NotBeNull(); 76 | actual.HasCondition.Should().BeTrue(); 77 | } 78 | 79 | [Theory] 80 | [InlineData(Query)] 81 | [InlineData(QueryDecoded)] 82 | public void Parse_GivenQuery_WhereMustContainTwoElements(string query) 83 | { 84 | Filter actual = query; 85 | 86 | actual.Where.Count.Should().Be(2); 87 | } 88 | 89 | [Theory] 90 | [InlineData(Query)] 91 | [InlineData(QueryDecoded)] 92 | public void Parse_GivenQuery_FieldsCannotBeNull(string query) 93 | { 94 | Filter actual = query; 95 | 96 | actual.Fields.Should().NotBeNull(); 97 | actual.HasVisualization.Should().BeTrue(); 98 | } 99 | 100 | [Theory] 101 | [InlineData(Query)] 102 | [InlineData(QueryDecoded)] 103 | public void Parse_GivenQuery_FieldsMustContainOneElement(string query) 104 | { 105 | Filter actual = query; 106 | 107 | actual.Fields.Count.Should().Be(1); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /test/unit/Http.Query.Filter.Client.Test/FilterBuilderTests.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Client.Test 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using FluentAssertions; 7 | 8 | using Http.Query.Filter.Client.Filters.Condition; 9 | 10 | using Xunit; 11 | 12 | using static System.Threading.Tasks.Task; 13 | 14 | using static Http.Query.Filter.Client.FilterBuilder; 15 | 16 | public class FilterBuilderTests 17 | { 18 | private readonly Func> done; 19 | 20 | public FilterBuilderTests() => this.done = expected => queryFilter => 21 | { 22 | queryFilter.Should().Be(expected); 23 | return CompletedTask; 24 | }; 25 | 26 | [Theory] 27 | [InlineData(2U, "filter[skip]=2")] 28 | public Task Skip_GivenFilter_ShouldReturn(uint skip, string expected) => NewFilterBuilder() 29 | .Skip(skip) 30 | .Build(this.done(expected)); 31 | 32 | [Theory] 33 | [InlineData(100U, "filter[limit]=100")] 34 | [InlineData(0U, "filter[limit]=1")] 35 | public Task Limit_GivenFilter_ShouldReturn(uint limit, string expected) => NewFilterBuilder() 36 | .Limit(limit) 37 | .Build(this.done(expected)); 38 | 39 | [Theory] 40 | [InlineData(new[] { "name", "id" }, "filter[fields][name]=true&filter[fields][id]=true")] 41 | [InlineData(new[] { "id" }, "filter[fields][id]=true")] 42 | public Task Select_GivenFilter_ShouldReturn(string[] fields, string expected) => NewFilterBuilder() 43 | .Select(fields) 44 | .Build(this.done(expected)); 45 | 46 | [Fact] 47 | public Task Equal_GivenFilter_ShouldReturn() => NewFilterBuilder() 48 | .Where("email".Equal("junoliv.e@gmail.com")) 49 | .Build(this.done("filter[where][email]=junoliv.e@gmail.com")); 50 | 51 | [Fact] 52 | public Task Gt_GivenFilter_ShouldReturn() => NewFilterBuilder() 53 | .Where("id".GreaterThan(10)) 54 | .Build(this.done("filter[where][id][gt]=10")); 55 | 56 | [Fact] 57 | public Task Lt_GivenFilter_ShouldReturn() => NewFilterBuilder() 58 | .Where("id".LessThan(10)) 59 | .Build(this.done("filter[where][id][lt]=10")); 60 | 61 | [Fact] 62 | public Task Inq_GivenFilter_ShouldReturn() => NewFilterBuilder() 63 | .Where("id".Inq(10, 11)) 64 | .Build(this.done("filter[where][id][inq]=10&filter[where][id][inq]=11")); 65 | 66 | [Fact] 67 | public Task And_GivenFilter_ShouldReturn() => NewFilterBuilder() 68 | .Where("id".GreaterThan(10) 69 | .And("email".Equal("junoliv.e@gmail.com"))) 70 | .Build(this.done("filter[where][and][id][gt]=10&filter[where][and][email]=junoliv.e@gmail.com")); 71 | 72 | [Fact] 73 | public Task Or_GivenFilter_ShouldReturn() => NewFilterBuilder() 74 | .Where("id".GreaterThan(10) 75 | .Or("email".Equal("junoliv.e@gmail.com"))) 76 | .Build(this.done("filter[where][or][id][gt]=10&filter[where][or][email]=junoliv.e@gmail.com")); 77 | 78 | [Fact] 79 | public Task OrAnd_GivenFilter_ShouldReturn() => NewFilterBuilder() 80 | .Where("id".GreaterThan(9) 81 | .Or("email".Equal("junoliv.e@gmail.com") 82 | .And("id".GreaterThan(10)))) 83 | .Build(this.done("filter[where][or][id][gt]=9&filter[where][and][email]=junoliv.e@gmail.com&filter[where][and][id][gt]=10")); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | * Demonstrating empathy and kindness toward other people 14 | * Being respectful of differing opinions, viewpoints, and experiences 15 | * Giving and gracefully accepting constructive feedback 16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | * Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | * The use of sexualized language or imagery, and sexual attention or 22 | advances of any kind 23 | * Trolling, insulting or derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or email 26 | address, without their explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a 28 | professional setting 29 | 30 | ## Enforcement Responsibilities 31 | 32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 33 | 34 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 35 | 36 | ## Scope 37 | 38 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 39 | 40 | ## Enforcement 41 | 42 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. 43 | 44 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 45 | 46 | ## Enforcement Guidelines 47 | 48 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 49 | 50 | ### 1. Correction 51 | 52 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 53 | 54 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 55 | 56 | ### 2. Warning 57 | 58 | **Community Impact**: A violation through a single incident or series of actions. 59 | 60 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 61 | 62 | ### 3. Temporary Ban 63 | 64 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 65 | 66 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 67 | 68 | ### 4. Permanent Ban 69 | 70 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 71 | 72 | **Consequence**: A permanent ban from any sort of public interaction within the community. 73 | 74 | ## Attribution 75 | 76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 77 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 78 | 79 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 80 | 81 | [homepage]: https://www.contributor-covenant.org 82 | 83 | For answers to common questions about this code of conduct, see the FAQ at 84 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 85 | -------------------------------------------------------------------------------- /docs/API.md: -------------------------------------------------------------------------------- 1 | ## API documentation 2 | 3 | Jump to: 4 | [limit](#limit-) | 5 | [skip](#skip-) | 6 | [order](#order-) | 7 | [where](#where-) | 8 | [fields](#fields-) 9 | 10 | ### Limit [↑](#api-documentation) 11 | 12 | A *limit* filter limits the number of records returned to the specified number (or less). 13 | 14 | **REST API** 15 | 16 | ``` 17 | ?filter[limit]=n 18 | ``` 19 | 20 | or RQL 21 | 22 | ``` 23 | ?limit=n 24 | ``` 25 | 26 | **Client API** 27 | 28 | ``` cs 29 | FilterBuilder 30 | .NewFilterBuilder() 31 | .Limit(n) 32 | .Build(); 33 | ``` 34 | 35 | Where *n* is the maximum number of results (records) to return. 36 | 37 | ### Skip [↑](#api-documentation) 38 | 39 | A *skip* filter omits the specified number of returned records. This is useful, for example, to paginate responses. 40 | 41 | **REST API** 42 | 43 | ``` 44 | ?filter[skip]=n 45 | ``` 46 | 47 | or RQL 48 | 49 | ``` 50 | ?skip=n 51 | ``` 52 | 53 | **Client API** 54 | 55 | ``` cs 56 | FilterBuilder 57 | .NewFilterBuilder() 58 | .Skip(n) 59 | .Build(); 60 | ``` 61 | 62 | Where *n* is the number of records to skip. 63 | 64 | ### Order [↑](#api-documentation) 65 | 66 | An *order* filter specifies how to sort the results: ascending (ASC) or descending (DESC) based on the specified property. 67 | 68 | #### by `ASC` 69 | 70 | **REST API** 71 | 72 | ``` 73 | ?filter[order]=property ASC 74 | ``` 75 | 76 | #### by `DESC` 77 | 78 | **REST API** 79 | 80 | ``` 81 | ?filter[order]=property DESC 82 | ``` 83 | 84 | #### by `ASC` and `DESC` 85 | 86 | Order by two or more properties: 87 | 88 | **REST API** 89 | 90 | ``` 91 | ?filter[order][0]=property ASC&filter[order][1]=property DESC 92 | ``` 93 | 94 | Where *property* is the name of the property (field) to sort by. 95 | 96 | ### Where [↑](#api-documentation) 97 | 98 | A *where* filter specifies a set of logical conditions to match, similar to a WHERE clause in a SQL query. 99 | 100 | #### Comparison Operators 101 | 102 | ##### Equivalence `=` 103 | 104 | **REST API** 105 | 106 | ``` 107 | ?filter[where][property]=value 108 | ``` 109 | 110 | **Client API** 111 | 112 | ``` cs 113 | FilterBuilder 114 | .NewFilterBuilder() 115 | .Where("property".Equal("value")) 116 | .Build(); 117 | ``` 118 | 119 | ##### Greater than `gt` 120 | 121 | **REST API** 122 | 123 | ``` 124 | ?filter[where][property][gt]=value 125 | ``` 126 | 127 | **Client API** 128 | 129 | ``` cs 130 | FilterBuilder 131 | .NewFilterBuilder() 132 | .Where("property".GreaterThan(value)) 133 | .Build(); 134 | ``` 135 | 136 | ##### Less than `lt` 137 | 138 | **REST API** 139 | 140 | ``` 141 | ?filter[where][property][lt]=value 142 | ``` 143 | 144 | **Client API** 145 | 146 | ``` cs 147 | FilterBuilder 148 | .NewFilterBuilder() 149 | .Where("property".LessThan(value)) 150 | .Build(); 151 | ``` 152 | 153 | Where: 154 | 155 | - *property* is the name of a property (field) in the model being queried. 156 | - *value* is a literal value. 157 | 158 | #### Logical Operators 159 | 160 | ##### And `and` 161 | 162 | **REST API** 163 | 164 | ``` 165 | ?filter[where][and][0][property]=value&filter[where][and][1][property][gt]=value 166 | ``` 167 | 168 | **Client API** 169 | 170 | ``` cs 171 | FilterBuilder 172 | .NewFilterBuilder() 173 | .Where("property".Equal("value") 174 | .And("property".GreaterThan(value))) 175 | .Build(); 176 | ``` 177 | 178 | ##### Or `or` 179 | 180 | **REST API** 181 | 182 | ``` 183 | ?filter[where][or][0][property]=value&filter[where][or][1][property][lt]=value 184 | ``` 185 | 186 | **Client API** 187 | 188 | ``` cs 189 | FilterBuilder 190 | .NewFilterBuilder() 191 | .Where("property".Equal("value") 192 | .Or("property".LessThan(value))) 193 | .Build(); 194 | ``` 195 | 196 | ##### And/Or 197 | 198 | ⚠ in development! 199 | 200 | **REST API** 201 | 202 | ``` 203 | ?filter[where][or][0][property][gt]=value&filter[where][and][1][property]=value&filter[where][and][2][property][gt]=value 204 | ``` 205 | 206 | **Client API** 207 | 208 | ``` cs 209 | FilterBuilder 210 | .NewFilterBuilder() 211 | .Where("property".GreaterThan(value) 212 | .Or("property".Equal("value") 213 | .And("property".GreaterThan(value)))) 214 | .Build(); 215 | ``` 216 | 217 | Where: 218 | 219 | - *property* is the name of a property (field) in the model being queried. 220 | - *value* is a literal value. 221 | 222 | ##### Inq 223 | 224 | The *inq* operator checks whether the value of the specified property matches any of the values provided in an array. 225 | 226 | **REST API** 227 | 228 | ``` 229 | ?filter[where][property][inq]=value1&filter[where][property][inq]=value2 230 | ``` 231 | 232 | **Client API** 233 | 234 | ``` cs 235 | FilterBuilder 236 | .NewFilterBuilder() 237 | .Where("property".Inq("value1", "value2")) 238 | .Build(); 239 | ``` 240 | 241 | Where: 242 | 243 | - *property* is the name of a property (field) in the model being queried. 244 | - *value1*, *value2*, and so on, are literal values in an array. 245 | 246 | ### Fields [↑](#api-documentation) 247 | 248 | A *fields* filter specifies properties (fields) to include from the results. 249 | 250 | **REST API** 251 | 252 | ``` 253 | ?filter[fields][property]=true 254 | ``` 255 | 256 | Order by two or more properties: 257 | 258 | ``` 259 | ?filter[fields][property]=true&filter[fields][property]=true 260 | ``` 261 | 262 | **Client API** 263 | 264 | ``` cs 265 | FilterBuilder 266 | .NewFilterBuilder() 267 | .Select(property) 268 | .Build(); 269 | ``` 270 | 271 | Order by two or more properties: 272 | 273 | ``` cs 274 | FilterBuilder 275 | .NewFilterBuilder() 276 | .Select(property, property) 277 | .Build(); 278 | ``` 279 | 280 | Where *property* is the name of the property (field) to include. 281 | 282 | By default, queries return all model properties in results. However, if you specify at least one fields filter with a value of `true`, then by default the query will include **only** those you specifically include with filters. 283 | -------------------------------------------------------------------------------- /test/integration/Http.Query.Filter.Integration.Test/AccountsTests.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Integration.Test 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | using FluentAssertions; 7 | 8 | using Http.Query.Filter.Client.Filters.Condition; 9 | using Http.Query.Filter.Integration.Test.Entities; 10 | using Http.Query.Filter.Integration.Test.Infrastructure.Data.Linq.Collections; 11 | using Http.Query.Filter.Integration.Test.Infrastructure.Data.Linq.Filter; 12 | using Http.Query.Filter.Integration.Test.Infrastructure.Data.Linq.Queries.Account; 13 | 14 | using Xunit; 15 | 16 | using static Http.Query.Filter.Client.FilterBuilder; 17 | 18 | public class AccountsTests 19 | { 20 | private readonly GetAllQuery getAll; 21 | private readonly IEnumerable accounts; 22 | 23 | public AccountsTests() 24 | { 25 | this.accounts = new Accounts(); 26 | this.getAll = new GetAllQuery(default(Skip), default(Limit)); 27 | } 28 | 29 | [Fact] 30 | public void GetAll_GivenQueryWithFilterSkip_ShouldReturn() 31 | { 32 | var expected = this.accounts.Skip(2); 33 | 34 | this.getAll 35 | .GetResult(NewFilterBuilder() 36 | .Skip(2) 37 | .Build()) 38 | .Data 39 | .Should() 40 | .BeEquivalentTo(expected); 41 | } 42 | 43 | [Fact] 44 | public void GetAll_GivenQueryWithFilterLimit_ShouldReturn() 45 | { 46 | var expected = this.accounts.Take(5); 47 | 48 | var actual = this.getAll 49 | .GetResult(NewFilterBuilder() 50 | .Limit(5) 51 | .Build()) 52 | .Data; 53 | 54 | actual.Should().BeEquivalentTo(expected); 55 | } 56 | 57 | [Fact] 58 | public void GetAll_GivenQueryWithFilterWhereEqualTo_ShouldReturn() 59 | { 60 | var expected = this.accounts.Where(item => item.Password == "333333"); 61 | 62 | var actual = this.getAll 63 | .GetResult(NewFilterBuilder() 64 | .Where("password".Equal("333333")) 65 | .Build()) 66 | .Data; 67 | 68 | actual.Should().BeEquivalentTo(expected); 69 | } 70 | 71 | [Fact] 72 | public void GetAll_GivenQueryWithFilterWhereGreaterThan_ShouldReturn() 73 | { 74 | var expected = this.accounts.Where(item => item.Id < 2); 75 | 76 | var actual = this.getAll 77 | .GetResult(NewFilterBuilder() 78 | .Where("id".LessThan(2)) 79 | .Build()) 80 | .Data; 81 | 82 | actual.Should().BeEquivalentTo(expected); 83 | } 84 | 85 | [Fact] 86 | public void GetAll_GivenQueryWithFilterWhereLessThan_ShouldReturn() 87 | { 88 | var expected = this.accounts.Where(item => item.Id > 5); 89 | 90 | var actual = this.getAll 91 | .GetResult(NewFilterBuilder() 92 | .Where("id".GreaterThan(5)) 93 | .Build()) 94 | .Data; 95 | 96 | actual.Should().BeEquivalentTo(expected); 97 | } 98 | 99 | [Fact] 100 | public void GetAll_GivenQueryWithFilterWhereInq_ShouldReturn() 101 | { 102 | var expected = this.accounts.Where(item => new[] { 10, 11 }.Contains(item.Id)); 103 | 104 | var actual = this.getAll 105 | .GetResult(NewFilterBuilder() 106 | .Where("id".Inq(10, 11)) 107 | .Build()) 108 | .Data; 109 | 110 | actual.Should().BeEquivalentTo(expected); 111 | } 112 | 113 | [Fact] 114 | public void GetAll_GivenQueryWithFilterWhereAnd_ShouldReturn() 115 | { 116 | var expected = this.accounts.Where(item => item.Id > 10 && item.Email == "junoliv.e@gmail.com"); 117 | 118 | var actual = this.getAll 119 | .GetResult(NewFilterBuilder() 120 | .Where("id".GreaterThan(10) 121 | .And("email".Equal("junoliv.e@gmail.com"))) 122 | .Build()) 123 | .Data; 124 | 125 | actual.Should().BeEquivalentTo(expected); 126 | } 127 | 128 | [Fact] 129 | public void GetAll_GivenQueryWithFilterWhereOr_ShouldReturn() 130 | { 131 | var expected = this.accounts.Where(item => item.Id > 10 || item.Email == "junoliv.e@gmail.com"); 132 | 133 | var actual = this.getAll 134 | .GetResult(NewFilterBuilder() 135 | .Where("id".GreaterThan(10) 136 | .Or("email".Equal("junoliv.e@gmail.com"))) 137 | .Build()) 138 | .Data; 139 | 140 | actual.Should().BeEquivalentTo(expected); 141 | } 142 | 143 | [Fact(Skip = "in development")] 144 | public void GetAll_GivenQueryWithFilterWhereOrAnd_ShouldReturn() 145 | { 146 | var expected = this.accounts.Where(item => item.Id > 9 || (item.Email == "junoliv.e@gmail.com" && item.Id > 10)); 147 | 148 | var actual = this.getAll 149 | .GetResult(NewFilterBuilder() 150 | .Where("id".GreaterThan(9) 151 | .Or("email".Equal("junoliv.e@gmail.com") 152 | .And("id".GreaterThan(10)))) 153 | .Build()) 154 | .Data; 155 | 156 | actual.Should().BeEquivalentTo(expected); 157 | } 158 | 159 | [Fact] 160 | public void GetAll_GivenQueryWithFilterSelect_ShouldReturn() 161 | { 162 | var expected = this.accounts 163 | .Select(account => new Dictionary 164 | { 165 | { "id", account.Id }, 166 | { "email", account.Email }, 167 | }) 168 | .ToList(); 169 | 170 | var actual = this.getAll 171 | .GetResult(NewFilterBuilder() 172 | .Select("id", "email") 173 | .Build()) 174 | .Data; 175 | 176 | actual.Should().BeEquivalentTo(expected); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Core EditorConfig Options # 3 | ############################### 4 | 5 | root = true 6 | 7 | # All files 8 | [*] 9 | indent_style = space 10 | insert_final_newline = true 11 | end_of_line = lf 12 | trim_trailing_whitespace = true 13 | 14 | # Code files 15 | [*.{cs,csx,vb,vbx}] 16 | indent_size = 4 17 | charset = utf-8-bom 18 | 19 | [*.{js,ts}] 20 | indent_size = 2 21 | charset = utf-8 22 | 23 | [*.md] 24 | trim_trailing_whitespace = false 25 | 26 | ############################### 27 | # .NET Coding Conventions # 28 | ############################### 29 | 30 | [*.{cs,vb}] 31 | # Organize usings 32 | dotnet_sort_system_directives_first = true 33 | 34 | # this. preferences 35 | dotnet_style_qualification_for_field = true:error 36 | dotnet_style_qualification_for_property = true:error 37 | dotnet_style_qualification_for_method = true:error 38 | dotnet_style_qualification_for_event = true:error 39 | 40 | # Language keywords vs BCL types preferences 41 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 42 | dotnet_style_predefined_type_for_member_access = true:error 43 | 44 | # Parentheses preferences 45 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 46 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 47 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 48 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 49 | 50 | # Modifier preferences 51 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:none 52 | dotnet_style_readonly_field = true:error 53 | 54 | # Expression-level preferences 55 | dotnet_style_object_initializer = true:error 56 | dotnet_style_collection_initializer = true:error 57 | dotnet_style_explicit_tuple_names = true:error 58 | dotnet_style_null_propagation = true:error 59 | dotnet_style_coalesce_expression = true:error 60 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent 61 | dotnet_prefer_inferred_tuple_names = true:error 62 | dotnet_prefer_inferred_anonymous_type_member_names = true:error 63 | dotnet_style_prefer_auto_properties = true:silent 64 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 65 | dotnet_style_prefer_conditional_expression_over_return = true:silent 66 | 67 | ############################### 68 | # Naming Conventions # 69 | ############################### 70 | 71 | # Style Definitions 72 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 73 | 74 | # Use PascalCase for constant fields 75 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 76 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 77 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 78 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 79 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = * 80 | dotnet_naming_symbols.constant_fields.required_modifiers = const 81 | 82 | ############################### 83 | # C# Coding Conventions # 84 | ############################### 85 | 86 | [*.cs] 87 | # var preferences 88 | csharp_style_var_for_built_in_types = true:error 89 | csharp_style_var_when_type_is_apparent = true:error 90 | csharp_style_var_elsewhere = true:error 91 | 92 | # Expression-bodied members 93 | csharp_style_expression_bodied_methods = true:error 94 | csharp_style_expression_bodied_constructors = true:error 95 | csharp_style_expression_bodied_operators = true:error 96 | csharp_style_expression_bodied_properties = true:error 97 | csharp_style_expression_bodied_indexers = true:error 98 | csharp_style_expression_bodied_accessors = true:error 99 | 100 | # Pattern matching preferences 101 | csharp_style_pattern_matching_over_is_with_cast_check = true:error 102 | csharp_style_pattern_matching_over_as_with_null_check = true:error 103 | 104 | # Null-checking preferences 105 | csharp_style_throw_expression = true:error 106 | csharp_style_conditional_delegate_call = true:error 107 | 108 | # Modifier preferences 109 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:error 110 | 111 | # Expression-level preferences 112 | csharp_prefer_braces = true:none 113 | csharp_style_deconstructed_variable_declaration = true:error 114 | csharp_prefer_simple_default_expression = true:error 115 | csharp_style_pattern_local_over_anonymous_function = true:error 116 | csharp_style_inlined_variable_declaration = true:error 117 | 118 | ############################### 119 | # C# Formatting Rules # 120 | ############################### 121 | 122 | # New line preferences 123 | csharp_new_line_before_open_brace = all 124 | csharp_new_line_before_else = true 125 | csharp_new_line_before_catch = true 126 | csharp_new_line_before_finally = true 127 | csharp_new_line_before_members_in_object_initializers = true 128 | csharp_new_line_before_members_in_anonymous_types = true 129 | csharp_new_line_between_query_expression_clauses = true 130 | 131 | # Indentation preferences 132 | csharp_indent_case_contents = true 133 | csharp_indent_switch_labels = true 134 | csharp_indent_labels = flush_left 135 | 136 | # Space preferences 137 | csharp_space_after_cast = false 138 | csharp_space_after_keywords_in_control_flow_statements = true 139 | csharp_space_between_method_call_parameter_list_parentheses = false 140 | csharp_space_between_method_declaration_parameter_list_parentheses = false 141 | csharp_space_between_parentheses = false 142 | csharp_space_before_colon_in_inheritance_clause = true 143 | csharp_space_after_colon_in_inheritance_clause = true 144 | csharp_space_around_binary_operators = before_and_after 145 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 146 | csharp_space_between_method_call_name_and_opening_parenthesis = false 147 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 148 | 149 | # Wrapping preferences 150 | csharp_preserve_single_line_statements = true 151 | csharp_preserve_single_line_blocks = true 152 | 153 | ############################### 154 | # VB Coding Conventions # 155 | ############################### 156 | 157 | [*.vb] 158 | # Modifier preferences 159 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:error 160 | -------------------------------------------------------------------------------- /test/unit/Http.Query.Filter.Test/Filters/WhereTests.cs: -------------------------------------------------------------------------------- 1 | namespace Http.Query.Filter.Test.Filters 2 | { 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | using FluentAssertions; 7 | 8 | using Http.Query.Filter.Filters.Condition; 9 | using Http.Query.Filter.Filters.Condition.Operators; 10 | 11 | using Xunit; 12 | 13 | using static System.String; 14 | using static Http.Query.Filter.Filters.Condition.Operators.Logical; 15 | 16 | public class WhereTests 17 | { 18 | [Theory] 19 | [ClassData(typeof(TestData))] 20 | public void Parse_GivenQuery_ShouldReturn(string query, Where expected) 21 | { 22 | Where actual = query; 23 | 24 | actual.Should().BeEquivalentTo(expected); 25 | } 26 | 27 | [Theory] 28 | [InlineData("?filter[condition][id]=1")] 29 | [InlineData("?filter[where][last name]=Test")] 30 | [InlineData("?filter[where][id][eq]=1")] 31 | [InlineData("?filter[where][id][equal]=1")] 32 | [InlineData("?filter[condition][id][gt]=1")] 33 | [InlineData("?filter[where][id][greaterthan]=1")] 34 | [InlineData("?filter[condition][id][lt]=1")] 35 | [InlineData("?filter[where][id][lessthan]=1")] 36 | public void Parse_GivenQuery_ShouldReturnEmpty(string query) 37 | { 38 | Where actual = query; 39 | 40 | actual.Should().BeEmpty(); 41 | } 42 | 43 | public class TestData : IEnumerable 44 | { 45 | public IEnumerator GetEnumerator() 46 | { 47 | // Equal to 48 | yield return new object[] { "?filter[where][id]=", Expected(new Condition("id", Empty)) }; 49 | yield return new object[] { "?filter[where][id]=2", Expected(new Condition("id", "2")) }; 50 | yield return new object[] { "?FILTER[WHERE][NAME]=NAME", Expected(new Condition("NAME", "NAME")) }; 51 | yield return new object[] { "?Filter[Where][Surname]=Surname", Expected(new Condition("Surname", "Surname")) }; 52 | yield return new object[] { "?filter%5Bwhere%5D%5Bid%5D=2", Expected(new Condition("id", "2")) }; 53 | 54 | // Greater than 55 | yield return new object[] { "?filter[where][id][gt]=", Expected(new Condition("id", Empty, Comparison.GreaterThan)) }; 56 | yield return new object[] { "?filter[where][id][gt]=2", Expected(new Condition("id", "2", Comparison.GreaterThan)) }; 57 | yield return new object[] { "?FILTER[WHERE][ID][GT]=2", Expected(new Condition("ID", "2", Comparison.GreaterThan)) }; 58 | yield return new object[] { "?Filter[Where][Id][Gt]=2", Expected(new Condition("Id", "2", Comparison.GreaterThan)) }; 59 | yield return new object[] { "?filter%5Bwhere%5D%5Bid%5D%5Bgt%5D=2", Expected(new Condition("id", "2", Comparison.GreaterThan)) }; 60 | 61 | // Less than 62 | yield return new object[] { "?filter[where][id][lt]=", Expected(new Condition("id", Empty, Comparison.LessThan)) }; 63 | yield return new object[] { "?filter[where][id][lt]=2", Expected(new Condition("id", "2", Comparison.LessThan)) }; 64 | yield return new object[] { "?FILTER[WHERE][ID][LT]=2", Expected(new Condition("ID", "2", Comparison.LessThan)) }; 65 | yield return new object[] { "?Filter[Where][Id][Lt]=2", Expected(new Condition("Id", "2", Comparison.LessThan)) }; 66 | yield return new object[] { "?filter%5Bwhere%5D%5Bid%5D%5Blt%5D=2", Expected(new Condition("id", "2", Comparison.LessThan)) }; 67 | 68 | yield return new object[] { "?filter[where][id][inq]=&filter[where][id][inq]=Empty", Expected(new Condition("id", Empty, Comparison.Inq), new Condition("id", "Empty", Comparison.Inq)) }; 69 | yield return new object[] { "?filter[where][id][inq]=2&filter[where][id][inq]=3", Expected(new Condition("id", "2", Comparison.Inq), new Condition("id", "3", Comparison.Inq)) }; 70 | yield return new object[] { "?FILTER[WHERE][ID][INQ]=2&FILTER[WHERE][ID][INQ]=3", Expected(new Condition("ID", "2", Comparison.Inq), new Condition("ID", "3", Comparison.Inq)) }; 71 | yield return new object[] { "?Filter[Where][Id][Inq]=2&Filter[Where][Id][Inq]=3", Expected(new Condition("Id", "2", Comparison.Inq), new Condition("Id", "3", Comparison.Inq)) }; 72 | yield return new object[] { "?filter%5Bwhere%5D%5Bid%5D%5Binq%5D=2&filter%5Bwhere%5D%5Bid%5D%5Binq%5D=3", Expected(new Condition("id", "2", Comparison.Inq), new Condition("id", "3", Comparison.Inq)) }; 73 | 74 | // And 75 | yield return new object[] { "?filter[where][and][0][id]=&filter[where][and][1][name]=", Expected(new Condition("id", Empty, And, 0), new Condition("name", Empty, And, 1)) }; 76 | yield return new object[] { "?filter[where][and][0][id]=2&filter[where][and][1][name]=junior", Expected(new Condition("id", "2", And, 0), new Condition("name", "junior", And, 1)) }; 77 | yield return new object[] { "?FILTER[WHERE][AND][0][ID]=2&FILTER[WHERE][AND][1][NAME]=junior", Expected(new Condition("ID", "2", And, 0), new Condition("NAME", "junior", And, 1)) }; 78 | yield return new object[] { "?Filter[Where][And][0][Id]=2&Filter[Where][And][1][Name]=junior", Expected(new Condition("Id", "2", And, 0), new Condition("Name", "junior", And, 1)) }; 79 | yield return new object[] { "?filter%5Bwhere%5D%5Band%5D%5B0%5D%5Bid%5D=2&filter%5Bwhere%5D%5Band%5D%5B1%5D%5Bname%5D=junior", Expected(new Condition("id", "2", And, 0), new Condition("name", "junior", And, 1)) }; 80 | 81 | // Or 82 | yield return new object[] { "?filter[where][or][0][id]=&filter[where][or][1][name]=", Expected(new Condition("id", Empty, Or, 0), new Condition("name", Empty, Or, 1)) }; 83 | yield return new object[] { "?filter[where][or][0][id]=2&filter[where][or][1][name]=junior", Expected(new Condition("id", "2", Or, 0), new Condition("name", "junior", Or, 1)) }; 84 | yield return new object[] { "?FILTER[WHERE][OR][0][ID]=2&FILTER[WHERE][OR][1][NAME]=junior", Expected(new Condition("ID", "2", Or, 0), new Condition("NAME", "junior", Or, 1)) }; 85 | yield return new object[] { "?Filter[Where][Or][0][Id]=2&Filter[Where][Or][1][Name]=junior", Expected(new Condition("Id", "2", Or, 0), new Condition("Name", "junior", Or, 1)) }; 86 | yield return new object[] { "?filter%5Bwhere%5D%5Bor%5D%5B0%5D%5Bid%5D=2&filter%5Bwhere%5D%5Bor%5D%5B1%5D%5Bname%5D=junior", Expected(new Condition("id", "2", Or, 0), new Condition("name", "junior", Or, 1)) }; 87 | } 88 | 89 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); 90 | 91 | private static Where Expected(params Condition[] conditions) => new Where(conditions); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Http.Query.Filter.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29418.71 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http.Query.Filter", "src\Http.Query.Filter\Http.Query.Filter.csproj", "{6D94C318-C94B-4DC5-A49B-89D26E1BF675}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2387706C-FD2E-4C04-B170-F1EAB4C41ECF}" 9 | ProjectSection(SolutionItems) = preProject 10 | .dockerignore = .dockerignore 11 | .editorconfig = .editorconfig 12 | .gitattributes = .gitattributes 13 | .gitignore = .gitignore 14 | .travis.yml = .travis.yml 15 | after.Http.Query.Filter.sln.targets = after.Http.Query.Filter.sln.targets 16 | appveyor.yml = appveyor.yml 17 | build.cake = build.cake 18 | build.ps1 = build.ps1 19 | build.sh = build.sh 20 | cake.config = cake.config 21 | CHANGELOG.md = CHANGELOG.md 22 | code-analysis.ruleset = code-analysis.ruleset 23 | CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md 24 | commitlint.config.js = commitlint.config.js 25 | CONTRIBUTING.md = CONTRIBUTING.md 26 | Directory.Build.props = Directory.Build.props 27 | Directory.Build.targets = Directory.Build.targets 28 | docker-compose-ci.yml = docker-compose-ci.yml 29 | Dockerfile = Dockerfile 30 | Http.Query.Filter.sln.DotSettings = Http.Query.Filter.sln.DotSettings 31 | LICENSE.txt = LICENSE.txt 32 | nuget.config = nuget.config 33 | package-lock.json = package-lock.json 34 | package.json = package.json 35 | README.md = README.md 36 | EndProjectSection 37 | EndProject 38 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{19DFDD00-586A-4FA8-A46C-D40BBE5F51B3}" 39 | ProjectSection(SolutionItems) = preProject 40 | test\Directory.Build.props = test\Directory.Build.props 41 | EndProjectSection 42 | EndProject 43 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unit", "unit", "{441E37A1-F116-4A2D-97B7-FD4B33F3C975}" 44 | EndProject 45 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http.Query.Filter.Test", "test\unit\Http.Query.Filter.Test\Http.Query.Filter.Test.csproj", "{1DDC5CA5-59F0-42D4-AA7A-665A39D74164}" 46 | EndProject 47 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integration", "integration", "{C9DEF327-8982-487B-87B4-074526DE468B}" 48 | EndProject 49 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http.Query.Filter.Integration.Test", "test\integration\Http.Query.Filter.Integration.Test\Http.Query.Filter.Integration.Test.csproj", "{C773E825-1DE8-416E-AAE9-4A309FB6B6DA}" 50 | EndProject 51 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http.Query.Filter.Client", "src\Http.Query.Filter.Client\Http.Query.Filter.Client.csproj", "{AB1C7DE8-6D4B-4EDE-A43E-688E1CD2E1F4}" 52 | EndProject 53 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http.Query.Filter.Client.Test", "test\unit\Http.Query.Filter.Client.Test\Http.Query.Filter.Client.Test.csproj", "{A8671134-6865-41E7-A80C-4190868BFB02}" 54 | EndProject 55 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{F6513FC2-3B98-4C8A-B8E7-8F8328518188}" 56 | ProjectSection(SolutionItems) = preProject 57 | docs\API.md = docs\API.md 58 | docs\logo.png = docs\logo.png 59 | docs\README.md = docs\README.md 60 | EndProjectSection 61 | EndProject 62 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6DD0B821-D69A-46D3-A903-0BAD6D5D5489}" 63 | ProjectSection(SolutionItems) = preProject 64 | build\build-solution.cake = build\build-solution.cake 65 | build\check-release-requirements.cake = build\check-release-requirements.cake 66 | build\ci-env.dev.json = build\ci-env.dev.json 67 | build\ci-env.json = build\ci-env.json 68 | build\data.cake = build\data.cake 69 | build\delete-temp-directories.cake = build\delete-temp-directories.cake 70 | build\destroy-ci-environment.cake = build\destroy-ci-environment.cake 71 | build\generate-changelog.cake = build\generate-changelog.cake 72 | build\generate-nuget-package.cake = build\generate-nuget-package.cake 73 | build\helpers.cake = build\helpers.cake 74 | build\mock-ci-environment.cake = build\mock-ci-environment.cake 75 | build\push-git-tag-and-changes.cake = build\push-git-tag-and-changes.cake 76 | build\push-nuget-package.cake = build\push-nuget-package.cake 77 | build\restore-nuget-packages.cake = build\restore-nuget-packages.cake 78 | build\run-tests.cake = build\run-tests.cake 79 | EndProjectSection 80 | EndProject 81 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{5D209901-76D5-4540-83B8-E7E16635CDC6}" 82 | ProjectSection(SolutionItems) = preProject 83 | tools\packages.config = tools\packages.config 84 | EndProjectSection 85 | EndProject 86 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6A80864B-EA1A-44EA-AF8C-936DC7AB0D37}" 87 | ProjectSection(SolutionItems) = preProject 88 | src\Directory.Build.props = src\Directory.Build.props 89 | EndProjectSection 90 | EndProject 91 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{672ED4EF-2696-4B2E-9D84-733F8197E9B0}" 92 | ProjectSection(SolutionItems) = preProject 93 | .nuget\nuget.exe = .nuget\nuget.exe 94 | EndProjectSection 95 | EndProject 96 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".config", ".config", "{FF96CE6C-3CB1-433F-992D-C8BB7B3EEEA6}" 97 | ProjectSection(SolutionItems) = preProject 98 | .config\dotnet-tools.json = .config\dotnet-tools.json 99 | EndProjectSection 100 | EndProject 101 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{9DE39003-79ED-4376-9671-10F034E237C7}" 102 | ProjectSection(SolutionItems) = preProject 103 | .github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md 104 | EndProjectSection 105 | EndProject 106 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{4EC7A05E-1D06-43F6-B048-7B7B8351010B}" 107 | ProjectSection(SolutionItems) = preProject 108 | .github\ISSUE_TEMPLATE\1-bug-report.md = .github\ISSUE_TEMPLATE\1-bug-report.md 109 | .github\ISSUE_TEMPLATE\2-feature-request.md = .github\ISSUE_TEMPLATE\2-feature-request.md 110 | .github\ISSUE_TEMPLATE\3-help.md = .github\ISSUE_TEMPLATE\3-help.md 111 | EndProjectSection 112 | EndProject 113 | Global 114 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 115 | Debug|Any CPU = Debug|Any CPU 116 | Release|Any CPU = Release|Any CPU 117 | EndGlobalSection 118 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 119 | {6D94C318-C94B-4DC5-A49B-89D26E1BF675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 120 | {6D94C318-C94B-4DC5-A49B-89D26E1BF675}.Debug|Any CPU.Build.0 = Debug|Any CPU 121 | {6D94C318-C94B-4DC5-A49B-89D26E1BF675}.Release|Any CPU.ActiveCfg = Release|Any CPU 122 | {6D94C318-C94B-4DC5-A49B-89D26E1BF675}.Release|Any CPU.Build.0 = Release|Any CPU 123 | {1DDC5CA5-59F0-42D4-AA7A-665A39D74164}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 124 | {1DDC5CA5-59F0-42D4-AA7A-665A39D74164}.Debug|Any CPU.Build.0 = Debug|Any CPU 125 | {1DDC5CA5-59F0-42D4-AA7A-665A39D74164}.Release|Any CPU.ActiveCfg = Release|Any CPU 126 | {1DDC5CA5-59F0-42D4-AA7A-665A39D74164}.Release|Any CPU.Build.0 = Release|Any CPU 127 | {C773E825-1DE8-416E-AAE9-4A309FB6B6DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 128 | {C773E825-1DE8-416E-AAE9-4A309FB6B6DA}.Debug|Any CPU.Build.0 = Debug|Any CPU 129 | {C773E825-1DE8-416E-AAE9-4A309FB6B6DA}.Release|Any CPU.ActiveCfg = Release|Any CPU 130 | {C773E825-1DE8-416E-AAE9-4A309FB6B6DA}.Release|Any CPU.Build.0 = Release|Any CPU 131 | {AB1C7DE8-6D4B-4EDE-A43E-688E1CD2E1F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 132 | {AB1C7DE8-6D4B-4EDE-A43E-688E1CD2E1F4}.Debug|Any CPU.Build.0 = Debug|Any CPU 133 | {AB1C7DE8-6D4B-4EDE-A43E-688E1CD2E1F4}.Release|Any CPU.ActiveCfg = Release|Any CPU 134 | {AB1C7DE8-6D4B-4EDE-A43E-688E1CD2E1F4}.Release|Any CPU.Build.0 = Release|Any CPU 135 | {A8671134-6865-41E7-A80C-4190868BFB02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 136 | {A8671134-6865-41E7-A80C-4190868BFB02}.Debug|Any CPU.Build.0 = Debug|Any CPU 137 | {A8671134-6865-41E7-A80C-4190868BFB02}.Release|Any CPU.ActiveCfg = Release|Any CPU 138 | {A8671134-6865-41E7-A80C-4190868BFB02}.Release|Any CPU.Build.0 = Release|Any CPU 139 | EndGlobalSection 140 | GlobalSection(SolutionProperties) = preSolution 141 | HideSolutionNode = FALSE 142 | EndGlobalSection 143 | GlobalSection(NestedProjects) = preSolution 144 | {6D94C318-C94B-4DC5-A49B-89D26E1BF675} = {6A80864B-EA1A-44EA-AF8C-936DC7AB0D37} 145 | {441E37A1-F116-4A2D-97B7-FD4B33F3C975} = {19DFDD00-586A-4FA8-A46C-D40BBE5F51B3} 146 | {1DDC5CA5-59F0-42D4-AA7A-665A39D74164} = {441E37A1-F116-4A2D-97B7-FD4B33F3C975} 147 | {C9DEF327-8982-487B-87B4-074526DE468B} = {19DFDD00-586A-4FA8-A46C-D40BBE5F51B3} 148 | {C773E825-1DE8-416E-AAE9-4A309FB6B6DA} = {C9DEF327-8982-487B-87B4-074526DE468B} 149 | {AB1C7DE8-6D4B-4EDE-A43E-688E1CD2E1F4} = {6A80864B-EA1A-44EA-AF8C-936DC7AB0D37} 150 | {A8671134-6865-41E7-A80C-4190868BFB02} = {441E37A1-F116-4A2D-97B7-FD4B33F3C975} 151 | {4EC7A05E-1D06-43F6-B048-7B7B8351010B} = {9DE39003-79ED-4376-9671-10F034E237C7} 152 | EndGlobalSection 153 | GlobalSection(ExtensibilityGlobals) = postSolution 154 | SolutionGuid = {821F894C-A052-461F-90B0-F4CD63105E74} 155 | EndGlobalSection 156 | EndGlobal 157 | -------------------------------------------------------------------------------- /code-analysis.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | --------------------------------------------------------------------------------