Skip to content

Commit 78cba0e

Browse files
authored
Merge pull request #275 from turingtestable/273
2 parents e8a0607 + d94287e commit 78cba0e

File tree

6 files changed

+128
-26
lines changed

6 files changed

+128
-26
lines changed

Diff for: src/JsonApiDotNetCore/Builders/LinkBuilder.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ public string GetRelatedRelationLink(string parent, string parentId, string chil
6464

6565
public string GetPageLink(int pageOffset, int pageSize)
6666
{
67-
return $"{_context.BasePath}/{_context.RequestEntity.EntityName}?page[size]={pageSize}&page[number]={pageOffset}";
67+
var filterQueryComposer = new QueryComposer();
68+
var filters = filterQueryComposer.Compose(_context);
69+
return $"{_context.BasePath}/{_context.RequestEntity.EntityName}?page[size]={pageSize}&page[number]={pageOffset}{filters}";
6870
}
6971
}
7072
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace JsonApiDotNetCore.Internal.Query{
2+
public static class QueryConstants {
3+
public const string FILTER = "filter";
4+
public const string SORT = "sort";
5+
public const string INCLUDE = "include";
6+
public const string PAGE = "page";
7+
public const string FIELDS = "fields";
8+
public const char OPEN_BRACKET = '[';
9+
public const char CLOSE_BRACKET = ']';
10+
public const char COMMA = ',';
11+
public const char COLON = ':';
12+
public const string COLON_STR = ":";
13+
14+
}
15+
}

Diff for: src/JsonApiDotNetCore/Services/QueryComposer.cs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Internal.Query;
3+
using Microsoft.Extensions.Logging;
4+
5+
namespace JsonApiDotNetCore.Services
6+
{
7+
public interface IQueryComposer
8+
{
9+
string Compose(IJsonApiContext jsonApiContext);
10+
}
11+
12+
public class QueryComposer : IQueryComposer
13+
{
14+
public string Compose(IJsonApiContext jsonApiContext)
15+
{
16+
string result = "";
17+
if(jsonApiContext != null && jsonApiContext.QuerySet != null)
18+
{
19+
List<FilterQuery> filterQueries = jsonApiContext.QuerySet.Filters;
20+
if (filterQueries.Count > 0)
21+
{
22+
foreach (FilterQuery filter in filterQueries)
23+
{
24+
result += ComposeSingleFilter(filter);
25+
}
26+
}
27+
}
28+
return result;
29+
}
30+
31+
private string ComposeSingleFilter(FilterQuery query)
32+
{
33+
var result = "&filter";
34+
result += QueryConstants.OPEN_BRACKET + query.Attribute + QueryConstants.CLOSE_BRACKET + query.Operation + query.Value;
35+
return result;
36+
}
37+
}
38+
}

Diff for: src/JsonApiDotNetCore/Services/QueryParser.cs

+14-25
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,6 @@ public class QueryParser : IQueryParser
2020
private readonly IControllerContext _controllerContext;
2121
private readonly JsonApiOptions _options;
2222

23-
private const string FILTER = "filter";
24-
private const string SORT = "sort";
25-
private const string INCLUDE = "include";
26-
private const string PAGE = "page";
27-
private const string FIELDS = "fields";
28-
private const char OPEN_BRACKET = '[';
29-
private const char CLOSE_BRACKET = ']';
30-
private const char COMMA = ',';
31-
private const char COLON = ':';
32-
private const string COLON_STR = ":";
33-
3423
public QueryParser(
3524
IControllerContext controllerContext,
3625
JsonApiOptions options)
@@ -46,35 +35,35 @@ public virtual QuerySet Parse(IQueryCollection query)
4635

4736
foreach (var pair in query)
4837
{
49-
if (pair.Key.StartsWith(FILTER))
38+
if (pair.Key.StartsWith(QueryConstants.FILTER))
5039
{
5140
if (disabledQueries.HasFlag(QueryParams.Filter) == false)
5241
querySet.Filters.AddRange(ParseFilterQuery(pair.Key, pair.Value));
5342
continue;
5443
}
5544

56-
if (pair.Key.StartsWith(SORT))
45+
if (pair.Key.StartsWith(QueryConstants.SORT))
5746
{
5847
if (disabledQueries.HasFlag(QueryParams.Sort) == false)
5948
querySet.SortParameters = ParseSortParameters(pair.Value);
6049
continue;
6150
}
6251

63-
if (pair.Key.StartsWith(INCLUDE))
52+
if (pair.Key.StartsWith(QueryConstants.INCLUDE))
6453
{
6554
if (disabledQueries.HasFlag(QueryParams.Include) == false)
6655
querySet.IncludedRelationships = ParseIncludedRelationships(pair.Value);
6756
continue;
6857
}
6958

70-
if (pair.Key.StartsWith(PAGE))
59+
if (pair.Key.StartsWith(QueryConstants.PAGE))
7160
{
7261
if (disabledQueries.HasFlag(QueryParams.Page) == false)
7362
querySet.PageQuery = ParsePageQuery(querySet.PageQuery, pair.Key, pair.Value);
7463
continue;
7564
}
7665

77-
if (pair.Key.StartsWith(FIELDS))
66+
if (pair.Key.StartsWith(QueryConstants.FIELDS))
7867
{
7968
if (disabledQueries.HasFlag(QueryParams.Fields) == false)
8069
querySet.Fields = ParseFieldsQuery(pair.Key, pair.Value);
@@ -94,9 +83,9 @@ protected virtual List<FilterQuery> ParseFilterQuery(string key, string value)
9483
// expected input = filter[id]=eq:1
9584
var queries = new List<FilterQuery>();
9685

97-
var propertyName = key.Split(OPEN_BRACKET, CLOSE_BRACKET)[1];
86+
var propertyName = key.Split(QueryConstants.OPEN_BRACKET, QueryConstants.CLOSE_BRACKET)[1];
9887

99-
var values = value.Split(COMMA);
88+
var values = value.Split(QueryConstants.COMMA);
10089
foreach (var val in values)
10190
{
10291
(var operation, var filterValue) = ParseFilterOperation(val);
@@ -111,7 +100,7 @@ protected virtual (string operation, string value) ParseFilterOperation(string v
111100
if (value.Length < 3)
112101
return (string.Empty, value);
113102

114-
var operation = value.Split(COLON);
103+
var operation = value.Split(QueryConstants.COLON);
115104

116105
if (operation.Length == 1)
117106
return (string.Empty, value);
@@ -121,7 +110,7 @@ protected virtual (string operation, string value) ParseFilterOperation(string v
121110
return (string.Empty, value);
122111

123112
var prefix = operation[0];
124-
value = string.Join(COLON_STR, operation.Skip(1));
113+
value = string.Join(QueryConstants.COLON_STR, operation.Skip(1));
125114

126115
return (prefix, value);
127116
}
@@ -132,7 +121,7 @@ protected virtual PageQuery ParsePageQuery(PageQuery pageQuery, string key, stri
132121
// page[number]=1
133122
pageQuery = pageQuery ?? new PageQuery();
134123

135-
var propertyName = key.Split(OPEN_BRACKET, CLOSE_BRACKET)[1];
124+
var propertyName = key.Split(QueryConstants.OPEN_BRACKET, QueryConstants.CLOSE_BRACKET)[1];
136125

137126
const string SIZE = "size";
138127
const string NUMBER = "number";
@@ -157,7 +146,7 @@ protected virtual List<SortQuery> ParseSortParameters(string value)
157146
var sortParameters = new List<SortQuery>();
158147

159148
const char DESCENDING_SORT_OPERATOR = '-';
160-
var sortSegments = value.Split(COMMA);
149+
var sortSegments = value.Split(QueryConstants.COMMA);
161150

162151
foreach (var sortSegment in sortSegments)
163152
{
@@ -189,14 +178,14 @@ protected virtual List<string> ParseIncludedRelationships(string value)
189178
throw new JsonApiException(400, "Deeply nested relationships are not supported");
190179

191180
return value
192-
.Split(COMMA)
181+
.Split(QueryConstants.COMMA)
193182
.ToList();
194183
}
195184

196185
protected virtual List<string> ParseFieldsQuery(string key, string value)
197186
{
198187
// expected: fields[TYPE]=prop1,prop2
199-
var typeName = key.Split(OPEN_BRACKET, CLOSE_BRACKET)[1];
188+
var typeName = key.Split(QueryConstants.OPEN_BRACKET, QueryConstants.CLOSE_BRACKET)[1];
200189

201190
const string ID = "Id";
202191
var includedFields = new List<string> { ID };
@@ -205,7 +194,7 @@ protected virtual List<string> ParseFieldsQuery(string key, string value)
205194
if (string.Equals(typeName, _controllerContext.RequestEntity.EntityName, StringComparison.OrdinalIgnoreCase) == false)
206195
return includedFields;
207196

208-
var fields = value.Split(COMMA);
197+
var fields = value.Split(QueryConstants.COMMA);
209198
foreach (var field in fields)
210199
{
211200
var attr = _controllerContext.RequestEntity

Diff for: test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/PagingTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class PagingTests
2323
private Faker<Person> _personFaker;
2424
private Faker<TodoItem> _todoItemFaker;
2525
private Faker<TodoItemCollection> _todoItemCollectionFaker;
26+
private DateTime CurrentTime;
2627

2728
public PagingTests(TestFixture<TestStartup> fixture)
2829
{

Diff for: test/UnitTests/Services/QueryComposerTests.cs

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Internal.Query;
3+
using JsonApiDotNetCore.Services;
4+
using Microsoft.AspNetCore.Http;
5+
using Moq;
6+
using Xunit;
7+
8+
namespace UnitTests.Services
9+
{
10+
public class QueryComposerTests
11+
{
12+
private readonly Mock<IJsonApiContext> _jsonApiContext;
13+
14+
public QueryComposerTests()
15+
{
16+
_jsonApiContext = new Mock<IJsonApiContext>();
17+
}
18+
19+
[Fact]
20+
public void Can_Compose_FilterStringForUrl()
21+
{
22+
// arrange
23+
var filter = new FilterQuery("attribute", "value", "=");
24+
var querySet = new QuerySet();
25+
List<FilterQuery> filters = new List<FilterQuery>();
26+
filters.Add(filter);
27+
querySet.Filters=filters;
28+
29+
_jsonApiContext
30+
.Setup(m => m.QuerySet)
31+
.Returns(querySet);
32+
33+
var queryComposer = new QueryComposer();
34+
// act
35+
var filterString = queryComposer.Compose(_jsonApiContext.Object);
36+
// assert
37+
Assert.Equal("&filter[attribute]=value", filterString);
38+
}
39+
40+
[Fact]
41+
public void NoFilter_Compose_EmptyStringReturned()
42+
{
43+
// arrange
44+
var querySet = new QuerySet();
45+
46+
_jsonApiContext
47+
.Setup(m => m.QuerySet)
48+
.Returns(querySet);
49+
50+
var queryComposer = new QueryComposer();
51+
// act
52+
var filterString = queryComposer.Compose(_jsonApiContext.Object);
53+
// assert
54+
Assert.Equal("", filterString);
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)