Skip to content

273 #275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 6, 2018
Merged

273 #275

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/JsonApiDotNetCore/Builders/LinkBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ public string GetRelatedRelationLink(string parent, string parentId, string chil

public string GetPageLink(int pageOffset, int pageSize)
{
return $"{_context.BasePath}/{_context.RequestEntity.EntityName}?page[size]={pageSize}&page[number]={pageOffset}";
var filterQueryComposer = new QueryComposer();
var filters = filterQueryComposer.Compose(_context);
return $"{_context.BasePath}/{_context.RequestEntity.EntityName}?page[size]={pageSize}&page[number]={pageOffset}{filters}";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably also include sort parameters, right?

}
}
}
15 changes: 15 additions & 0 deletions src/JsonApiDotNetCore/Internal/Query/QueryConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace JsonApiDotNetCore.Internal.Query{
public static class QueryConstants {
public const string FILTER = "filter";
public const string SORT = "sort";
public const string INCLUDE = "include";
public const string PAGE = "page";
public const string FIELDS = "fields";
public const char OPEN_BRACKET = '[';
public const char CLOSE_BRACKET = ']';
public const char COMMA = ',';
public const char COLON = ':';
public const string COLON_STR = ":";

}
}
38 changes: 38 additions & 0 deletions src/JsonApiDotNetCore/Services/QueryComposer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Collections.Generic;
using JsonApiDotNetCore.Internal.Query;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCore.Services
{
public interface IQueryComposer
{
string Compose(IJsonApiContext jsonApiContext);
}

public class QueryComposer : IQueryComposer
{
public string Compose(IJsonApiContext jsonApiContext)
{
string result = "";
if(jsonApiContext != null && jsonApiContext.QuerySet != null)
{
List<FilterQuery> filterQueries = jsonApiContext.QuerySet.Filters;
if (filterQueries.Count > 0)
{
foreach (FilterQuery filter in filterQueries)
{
result += ComposeSingleFilter(filter);
}
}
}
return result;
}

private string ComposeSingleFilter(FilterQuery query)
{
var result = "&filter";
result += QueryConstants.OPEN_BRACKET + query.Attribute + QueryConstants.CLOSE_BRACKET + query.Operation + query.Value;
return result;
}
}
}
39 changes: 14 additions & 25 deletions src/JsonApiDotNetCore/Services/QueryParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,6 @@ public class QueryParser : IQueryParser
private readonly IControllerContext _controllerContext;
private readonly JsonApiOptions _options;

private const string FILTER = "filter";
private const string SORT = "sort";
private const string INCLUDE = "include";
private const string PAGE = "page";
private const string FIELDS = "fields";
private const char OPEN_BRACKET = '[';
private const char CLOSE_BRACKET = ']';
private const char COMMA = ',';
private const char COLON = ':';
private const string COLON_STR = ":";

public QueryParser(
IControllerContext controllerContext,
JsonApiOptions options)
Expand All @@ -46,35 +35,35 @@ public virtual QuerySet Parse(IQueryCollection query)

foreach (var pair in query)
{
if (pair.Key.StartsWith(FILTER))
if (pair.Key.StartsWith(QueryConstants.FILTER))
{
if (disabledQueries.HasFlag(QueryParams.Filter) == false)
querySet.Filters.AddRange(ParseFilterQuery(pair.Key, pair.Value));
continue;
}

if (pair.Key.StartsWith(SORT))
if (pair.Key.StartsWith(QueryConstants.SORT))
{
if (disabledQueries.HasFlag(QueryParams.Sort) == false)
querySet.SortParameters = ParseSortParameters(pair.Value);
continue;
}

if (pair.Key.StartsWith(INCLUDE))
if (pair.Key.StartsWith(QueryConstants.INCLUDE))
{
if (disabledQueries.HasFlag(QueryParams.Include) == false)
querySet.IncludedRelationships = ParseIncludedRelationships(pair.Value);
continue;
}

if (pair.Key.StartsWith(PAGE))
if (pair.Key.StartsWith(QueryConstants.PAGE))
{
if (disabledQueries.HasFlag(QueryParams.Page) == false)
querySet.PageQuery = ParsePageQuery(querySet.PageQuery, pair.Key, pair.Value);
continue;
}

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

var propertyName = key.Split(OPEN_BRACKET, CLOSE_BRACKET)[1];
var propertyName = key.Split(QueryConstants.OPEN_BRACKET, QueryConstants.CLOSE_BRACKET)[1];

var values = value.Split(COMMA);
var values = value.Split(QueryConstants.COMMA);
foreach (var val in values)
{
(var operation, var filterValue) = ParseFilterOperation(val);
Expand All @@ -111,7 +100,7 @@ protected virtual (string operation, string value) ParseFilterOperation(string v
if (value.Length < 3)
return (string.Empty, value);

var operation = value.Split(COLON);
var operation = value.Split(QueryConstants.COLON);

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

var prefix = operation[0];
value = string.Join(COLON_STR, operation.Skip(1));
value = string.Join(QueryConstants.COLON_STR, operation.Skip(1));

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

var propertyName = key.Split(OPEN_BRACKET, CLOSE_BRACKET)[1];
var propertyName = key.Split(QueryConstants.OPEN_BRACKET, QueryConstants.CLOSE_BRACKET)[1];

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

const char DESCENDING_SORT_OPERATOR = '-';
var sortSegments = value.Split(COMMA);
var sortSegments = value.Split(QueryConstants.COMMA);

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

return value
.Split(COMMA)
.Split(QueryConstants.COMMA)
.ToList();
}

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

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

var fields = value.Split(COMMA);
var fields = value.Split(QueryConstants.COMMA);
foreach (var field in fields)
{
var attr = _controllerContext.RequestEntity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class PagingTests
private Faker<Person> _personFaker;
private Faker<TodoItem> _todoItemFaker;
private Faker<TodoItemCollection> _todoItemCollectionFaker;
private DateTime CurrentTime;

public PagingTests(TestFixture<TestStartup> fixture)
{
Expand Down
57 changes: 57 additions & 0 deletions test/UnitTests/Services/QueryComposerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Collections.Generic;
using JsonApiDotNetCore.Internal.Query;
using JsonApiDotNetCore.Services;
using Microsoft.AspNetCore.Http;
using Moq;
using Xunit;

namespace UnitTests.Services
{
public class QueryComposerTests
{
private readonly Mock<IJsonApiContext> _jsonApiContext;

public QueryComposerTests()
{
_jsonApiContext = new Mock<IJsonApiContext>();
}

[Fact]
public void Can_Compose_FilterStringForUrl()
{
// arrange
var filter = new FilterQuery("attribute", "value", "=");
var querySet = new QuerySet();
List<FilterQuery> filters = new List<FilterQuery>();
filters.Add(filter);
querySet.Filters=filters;

_jsonApiContext
.Setup(m => m.QuerySet)
.Returns(querySet);

var queryComposer = new QueryComposer();
// act
var filterString = queryComposer.Compose(_jsonApiContext.Object);
// assert
Assert.Equal("&filter[attribute]=value", filterString);
}

[Fact]
public void NoFilter_Compose_EmptyStringReturned()
{
// arrange
var querySet = new QuerySet();

_jsonApiContext
.Setup(m => m.QuerySet)
.Returns(querySet);

var queryComposer = new QueryComposer();
// act
var filterString = queryComposer.Compose(_jsonApiContext.Object);
// assert
Assert.Equal("", filterString);
}
}
}