├── 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