Skip to content

Commit 1d13825

Browse files
authored
Merge pull request #499 from joshhubers/feat/#482
Feat/#482
2 parents 11fe0da + f9a8580 commit 1d13825

File tree

5 files changed

+111
-5
lines changed

5 files changed

+111
-5
lines changed

docs/usage/resources/resource-definitions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ public class ItemResource : ResourceDefinition<Item>
113113
// handles queries like: ?filter[was-active-on]=2018-10-15T01:25:52Z
114114
public override QueryFilters GetQueryFilters()
115115
=> new QueryFilters {
116-
{ "was-active-on", (items, value) => DateTime.TryParse(value, out dateValue)
116+
{ "was-active-on", (items, filter) => DateTime.TryParse(filter.Value, out dateValue)
117117
? items.Where(i => i.Expired == null || dateValue < i.Expired)
118-
: throw new JsonApiException(400, $"'{value}' is not a valid date.")
118+
: throw new JsonApiException(400, $"'{filter.Value}' is not a valid date.")
119119
}
120120
};
121121
}
@@ -128,4 +128,4 @@ Prior to the introduction of auto-discovery, you needed to register the
128128

129129
```c#
130130
services.AddScoped<ResourceDefinition<Item>, ItemResource>();
131-
```
131+
```
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
11
using System.Collections.Generic;
2+
using System.Linq;
23
using JsonApiDotNetCore.Models;
34
using JsonApiDotNetCoreExample.Models;
5+
using JsonApiDotNetCore.Internal.Query;
46

57
namespace JsonApiDotNetCoreExample.Resources
68
{
79
public class UserResource : ResourceDefinition<User>
810
{
911
protected override List<AttrAttribute> OutputAttrs()
1012
=> Remove(user => user.Password);
13+
14+
public override QueryFilters GetQueryFilters()
15+
{
16+
return new QueryFilters
17+
{
18+
{ "first-character", (users, queryFilter) => FirstCharacterFilter(users, queryFilter) }
19+
};
20+
}
21+
22+
private IQueryable<User> FirstCharacterFilter(IQueryable<User> users, FilterQuery filterQuery)
23+
{
24+
switch(filterQuery.Operation)
25+
{
26+
case "lt":
27+
return users.Where(u => u.Username[0] < filterQuery.Value[0]);
28+
default:
29+
return users.Where(u => u.Username[0] == filterQuery.Value[0]);
30+
}
31+
}
1132
}
1233
}

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public virtual IQueryable<TEntity> Filter(IQueryable<TEntity> entities, FilterQu
108108
var defaultQueryFilters = _resourceDefinition.GetQueryFilters();
109109
if (defaultQueryFilters != null && defaultQueryFilters.TryGetValue(filterQuery.Attribute, out var defaultQueryFilter) == true)
110110
{
111-
return defaultQueryFilter(entities, filterQuery.Value);
111+
return defaultQueryFilter(entities, filterQuery);
112112
}
113113
}
114114

src/JsonApiDotNetCore/Models/ResourceDefinition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ private List<AttrAttribute> GetOutputAttrs()
162162
/// method signature.
163163
/// See <see cref="GetQueryFilters" /> for usage details.
164164
/// </summary>
165-
public class QueryFilters : Dictionary<string, Func<IQueryable<T>, string, IQueryable<T>>> { }
165+
public class QueryFilters : Dictionary<string, Func<IQueryable<T>, FilterQuery, IQueryable<T>>> { }
166166

167167
/// <summary>
168168
/// Define a the default sort order if no sort key is provided.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Net;
4+
using System.Net.Http;
5+
using System.Net.Http.Headers;
6+
using System.Threading.Tasks;
7+
using Bogus;
8+
using JsonApiDotNetCore.Models;
9+
using JsonApiDotNetCore.Serialization;
10+
using JsonApiDotNetCoreExample.Data;
11+
using JsonApiDotNetCoreExample.Models;
12+
using Microsoft.EntityFrameworkCore;
13+
using Newtonsoft.Json;
14+
using Xunit;
15+
16+
namespace JsonApiDotNetCoreExampleTests.Acceptance
17+
{
18+
[Collection("WebHostCollection")]
19+
public class QueryFiltersTests
20+
{
21+
private TestFixture<TestStartup> _fixture;
22+
private AppDbContext _context;
23+
private Faker<User> _userFaker;
24+
25+
public QueryFiltersTests(TestFixture<TestStartup> fixture)
26+
{
27+
_fixture = fixture;
28+
_context = fixture.GetService<AppDbContext>();
29+
_userFaker = new Faker<User>()
30+
.RuleFor(u => u.Username, f => f.Internet.UserName())
31+
.RuleFor(u => u.Password, f => f.Internet.Password());
32+
}
33+
34+
[Fact]
35+
public async Task FiltersWithCustomQueryFiltersEquals()
36+
{
37+
// Arrange
38+
var user = _userFaker.Generate();
39+
var firstUsernameCharacter = user.Username[0];
40+
_context.Users.Add(user);
41+
_context.SaveChanges();
42+
43+
var httpMethod = new HttpMethod("GET");
44+
var route = $"/api/v1/users?filter[first-character]=eq:{firstUsernameCharacter}";
45+
var request = new HttpRequestMessage(httpMethod, route);
46+
47+
// Act
48+
var response = await _fixture.Client.SendAsync(request);
49+
50+
// Assert
51+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
52+
var body = await response.Content.ReadAsStringAsync();
53+
var deserializedBody = _fixture.GetService<IJsonApiDeSerializer>().DeserializeList<User>(body);
54+
var usersWithFirstCharacter = _context.Users.Where(u => u.Username[0] == firstUsernameCharacter);
55+
Assert.True(deserializedBody.All(u => u.Username[0] == firstUsernameCharacter));
56+
}
57+
58+
[Fact]
59+
public async Task FiltersWithCustomQueryFiltersLessThan()
60+
{
61+
// Arrange
62+
var aUser = _userFaker.Generate();
63+
aUser.Username = "alfred";
64+
var zUser = _userFaker.Generate();
65+
zUser.Username = "zac";
66+
_context.Users.AddRange(aUser, zUser);
67+
_context.SaveChanges();
68+
69+
var median = 'h';
70+
71+
var httpMethod = new HttpMethod("GET");
72+
var route = $"/api/v1/users?filter[first-character]=lt:{median}";
73+
var request = new HttpRequestMessage(httpMethod, route);
74+
75+
// Act
76+
var response = await _fixture.Client.SendAsync(request);
77+
78+
// Assert
79+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
80+
var body = await response.Content.ReadAsStringAsync();
81+
var deserializedBody = _fixture.GetService<IJsonApiDeSerializer>().DeserializeList<User>(body);
82+
Assert.True(deserializedBody.All(u => u.Username[0] < median));
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)